Skip to content

[Bug]: adapter and runTurn report send-budget exhaustion as 502 upstream_error #4708

Closed

Description

@lidge-jun

Client or integration

Other

Area

Streaming

Summary

When a request exhausts its shared send budget, the three dispatch paths disagree about what the client is told.

The passthrough path returns 429 request_send_budget_exhausted and explicitly declines to blame the provider (src/server/responses/passthrough-dispatch.ts:693-700). The adapter path does not special-case SendBudgetExhaustedError: it falls through describeUpstreamConnectFailure and surfaces 502 upstream_error — "Provider unreachable" — for a refusal this proxy made itself (src/server/responses/adapter-dispatch.ts:331-335, :483-488). The runTurn path pushes a generic error event into the SSE bridge, so the client sees response.failed under HTTP 200 (src/server/responses/run-turn-execution.ts:167-180).

Separately, the terminal-guard continuation loop never consults sendBudgetExhausted() (src/server/responses/adapter-continuation.ts:260-264) while the main recovery loop does (adapter-dispatch.ts:593), so a spent budget can still same-key 429-replay on a live stream.

Both predate 2.56.0; they are recorded here so the disagreement is tracked rather than rediscovered.

Reproduction

  1. Configure a routed provider whose adapter runs inner retries (Kiro or Cursor, or any provider with a transient retry policy).
  2. Drive a request until the shared request send budget is spent, then force one more recovery leg.
  3. Observe 502 upstream_error from the adapter path where the passthrough path would have returned 429 request_send_budget_exhausted.

Suggested fix: in both adapter catch sites, check err instanceof SendBudgetExhaustedError before describeUpstreamConnectFailure and return the same 429 the passthrough path returns; map the runTurn case to a 429 before the SSE response is committed; and gate the continuation 429 loop on sendBudgetExhausted().

Version

2.56.0 (e4a8539)

Operating system

macOS 15.5

Provider and model

kiro / any; also reproduces on providers with a transient retry policy

Logs or error output

Provider unreachable: <host> (observed where a 429 request_send_budget_exhausted was expected)

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Activity

  1. lidge-jun commented on Sep 15, 2026

    @lidge-jun
    OwnerAuthor

    리뷰 · 우선순위 73 / 80

    이 이슈는 공유 send budget이 바닥났을 때 세 dispatch 경로가 클라이언트에게 다른 답을 주는 불일치다. tip 51d577c3f에서 passthrough는 429 request_send_budget_exhausted를 돌려 공급자 탓을 하지 않는다(src/server/responses/passthrough-dispatch.ts). 어댑터 경로(adapter-dispatch.ts 약 331–335, 483–502행)는 SendBudgetExhaustedError를 특별 처리하지 않고 describeUpstreamConnectFailure로 떨어뜨려 502 upstream_error(Provider unreachable)를 만든다. run-turn-execution.ts catch는 pacing overload만 429로 매핑하고, 그 외 에러는 SSE error 이벤트로 밀어 HTTP 200 위 response.failed처럼 보이게 한다.

    추가로 터미널 가드 continuation(adapter-continuation.ts 약 258–270행)의 same-key 429 루프는 sendBudgetExhausted()를 보지 않는다. 본선 recovery 루프(adapter-dispatch 약 513·608행)는 본다. 예산이 이미 소진된 뒤에도 live stream continuation이 same-key 429 replay를 시도할 수 있다. 둘 다 2.56.0 이전부터 있던 부채로, 감사에서 ‘다시 발견하지 말자’고 이슈화한 형태다.

    운영 영향은 크다. 프록시가 스스로 거절한 요청을 upstream 장애로 보이게 하면 재시도·알람·풀 회전이 잘못 움직인다. 수정은 이슈 제안이 명확하다. 어댑터 catch 두 곳에서 instanceof SendBudgetExhaustedError를 먼저 보고 passthrough와 같은 429를 돌려주고, runTurn도 SSE commit 전에 429로 매핑하며, continuation 429 루프에 sendBudgetExhausted() 가드를 넣는다.

    경로 src/server/responses/adapter-dispatch.ts catch - budget 소진을 502 upstream_error로 위장.
    경로 src/server/responses/run-turn-execution.ts - SendBudgetExhaustedError가 generic SSE error로 떨어짐.
    경로 src/server/responses/adapter-continuation.ts same-key 429 while - sendBudgetExhausted() 미상담.
    경로 passthrough-dispatch.ts - 이미 올바른 429 계약. 기준으로 삼을 것.

    메인테이너의 판단이 필요한 지점

    • 클라이언트에 노출하는 code/message를 passthrough와 바이트 단위로 맞출지.
    • continuation 가드만 먼저 넣을지, 세 경로 매핑을 한 PR에 묶을지.
    • 관측(로그/메트릭)에 self-refusal 태그를 남길지.

    너의 추천
    받아라. 한 PR로 어댑터 두 catch + runTurn 매핑 + continuation 가드를 맞추고, Kiro/Cursor 예산 소진 픽스처로 429 코드가 세 경로에서 같은지 검증하라.

    이 댓글은 grok-bot이 작성했습니다

  2. added a commit that references this issue on Sep 16, 2026
  3. lidge-jun commented on Sep 16, 2026

    @lidge-jun
    OwnerAuthor

    Landed via #4758 at 6e254f3.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinglanded-via-maintainerOriginal PR closed after landing via a maintainer merge trainstreamingSSE, WebSocket, terminal stream frames

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions