Skip to content

fix(channel): auto-recover from 529 and normalize cross-provider tool_use ids#31

Merged
epicsagas merged 2 commits into
mainfrom
fix/channel-session-stability
Jun 29, 2026
Merged

fix(channel): auto-recover from 529 and normalize cross-provider tool_use ids#31
epicsagas merged 2 commits into
mainfrom
fix/channel-session-stability

Conversation

@epicsagas

Copy link
Copy Markdown
Owner

Summary

두 가지 채널 세션 안정성 개선입니다.

1. 529(서버 과부하) 자동 복구

Telegram 등 채널에서 Claude API가 529 Overloaded를 반환한 뒤 세션이 복구되지 않던 문제를 해결합니다. 이전에는 컨텍스트 한계(context_limit)에만 자동 복구 경로가 있었습니다.

  • 감지: is_transient_api_error() + TransientApiError 추가 — 529/429/503/overloaded/rate limit 패턴과 stream-json의 is_error: true 플래그 모두 인식 (stream_handler.rs)
  • 복구: handle_transient_api_recovery() — 동일 세션 --resume으로 지수 백오프 + jitter 자동 재시도 (최대 3회, recovery_depth로 무한 루프 방지) (event_dispatch.rs)
  • jitter: RetryPolicy::delay_for_attempt()에 Full Jitter(AWS 패턴) 구현 — 이전엔 jitter 필드가 선언만 되고 미사용 (retry.rs)
  • PID 좀비 방지: thinking 메시지 전송 실패 시 tracked Claude PID를 정리해 이후 "Busy" 락 방지 (event_dispatch.rs)

동작: 529 감지 → raw 에러 표시 → "Retrying in Xs (1/3)..." → 백오프 후 동일 세션 재시도 → 성공 시 정상 응답, 3회 실패 시 안내.

2. provider 교차 세션 resume 호환성

claude -cclaudy <zai-profile> -c를 같은 디렉토리에서 번갈아 쓸 때 최근 세션이 안 이어지던 문제를 해결합니다.

루트 코스 (실제 세션 파일에서 검증):

  • zai(glm) 세션: tool_use.id = call_<hex> (OpenAI 스타일)
  • claude(Anthropic) 세션: tool_use.id = toolu_<base62> (Anthropic 표준)

Anthropic API는 toolu_ 패턴을 요구하므로, zai 세션을 claude로 resume하면 history의 call_* ID로 400 거부 → 세션 로드 실패 → "최근 세션이 안 보임".

  • sanitize_session_tool_use_ids(): 기존 server_tool_use sanitizer와 동일한 2-pass 패턴으로 call_*toolu_* 변환. assistant의 tool_use.id와 user의 tool_result.tool_use_id 양쪽을 atomic 재작성. type=="tool_use"만 처리해 server_tool_use와 충돌 방지 (sessions.rs)
  • sanitize_session() 통합 → resume 직전 자동 실행 (3개 호출부 변경 없이 자동 적용)
  • 3개 sanitize 로그에 tool_use_ids=N 필드 추가

Test plan

  • cargo fmt
  • cargo clippy -- -D warnings
  • cargo test — 346 unit + 11 integration, 전부 통과 (+18 신규 테스트)
  • 수동: zai로 작업 후 같은 디렉토리에서 claude -c → resume 직전 tool_use_ids=N 로그 → 세션 정상 이어짐
  • 수동: 529 발생 시 백오프 재시도 동작 확인

Notes

  • mode는 독립 운용이 맞으므로 건드리지 않았습니다 (mode별 CLAUDE_CONFIG_DIR 분리는 의도된 동작).
  • profile은 세션 경로를 변경하지 않으므로, mode 비선택 시 양쪽이 같은 세션 풀을 공유합니다.

…_use ids

Two related channel-session stability fixes:

1. 529 (overloaded) auto-recovery
   - Detect transient API errors (529/429/503/overloaded) via new
     is_transient_api_error() + TransientApiError, honoring the
     stream-json "is_error" flag (stream_handler.rs)
   - handle_transient_api_recovery(): jittered exponential backoff retry
     of the same --resume session, bounded by recovery_depth (event_dispatch.rs)
   - Implement Full Jitter in RetryPolicy::delay_for_attempt() (retry.rs)
   - Clean up the tracked Claude PID when the thinking-message send fails,
     preventing "Busy" lock from a lingering zombie PID (event_dispatch.rs)

2. Cross-provider session resume compatibility
   - Anthropic API requires toolu_* tool_use ids; ZAI/GLM emit call_* (OpenAI
     style), causing HTTP 400 on resume -> "recent session not found"
   - sanitize_session_tool_use_ids(): 2-pass remap of call_* -> toolu_* on
     both assistant tool_use.id and user tool_result.tool_use_id (sessions.rs)
   - Wired into sanitize_session(); surfaces in all 3 sanitize log sites

Tests: +18 unit tests across stream_handler/retry/sessions.
fmt/clippy clean, full suite green (346 + 11).
- Gate transient-API classification on stream-json is_error so a normal
  assistant result mentioning 'rate limit'/'overloaded' is never discarded
  and replayed as a 529 (removes the tautological is_error dead branch)
- Split context-limit and transient recovery into independent depth counters
  so a transient retry can no longer exhaust the compaction budget and clear
  the session
- Kill (not just untrack) the tracked Claude PID before recovery re-entry,
  and dedupe the two transient entry blocks into one helper
- Read session JSONL once in sanitize_session and reuse parsed content
  across all sanitizer cores; unify the two id validators
@epicsagas epicsagas merged commit 572d930 into main Jun 29, 2026
5 checks passed
@epicsagas epicsagas deleted the fix/channel-session-stability branch June 29, 2026 07:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant