Repository navigation
[Bug]: Retrying a Claude turn while still limited reports "gave up after repeated API errors" again #10544
Description
Activity
Triage
Confirmed on current main. Follow-up gap in #10321, not a duplicate of #10320.
#10321 latches two causes so a generic
terminal_reason: "api_error"can name the real problem:- assistant
error: "authentication_failed"→ signed-out copy rate_limit_eventwithstatus: "rejected"→turnState.rejectedRateLimitTypes→ “Claude usage limit reached. Send the message again once the limit resets.”
The CLI only emits
rate_limit_eventwhen the limit state changes. A retry while still limited has no new event. It sends:assistant { error: "rate_limit", message.content[].text: "You've hit your session limit · resets …" } result { subtype: "success", is_error: true, terminal_reason: "api_error" }handleAssistantMessageinapps/server/src/provider/Layers/ClaudeAdapter.tskeepsauthentication_failedand ignoresrate_limit.handleResultMessagethen has an emptyrejectedRateLimitTypesand falls back to “Claude gave up after repeated API errors.” Adapter tests cover rejectedrate_limit_eventand the auth-assistant path; they do not cover assistanterror: "rate_limit"without an event.The CLI line still appears as normal assistant text, and the first limited turn’s copy stays above, so this is a minor mapping bug. Same class as #10320, retry-only.
Related
- [Bug]: Claude reports "gave up after repeated API errors" for an expired login or usage limit #10320 / fix(claude): name the expired login or usage limit instead of a generic API error #10321 — first-turn login/quota remapping (quota half assumed a rejected
rate_limit_event) - fix(claude): surface usage-limit pauses in the thread #7165 — warning row from rejected
rate_limit_event(also skipped on these retries) - fix(claude): fail turns when Claude Code is logged out #8869, [Bug]: Claude thread stays logged out after re-login until its CLI process is reaped #9607 / fix(server): respawn Claude sessions after re-login on expired credentials #9628 — auth probe / stale CLI; leave on those tickets
Suggested fix
Adapter-only, same latch pattern as auth. On
message.error === "rate_limit", record usage-limit evidence for the current turn (respect the existingparent_tool_use_idguard). Reuse the existing result sentence. Do not override listed errors, 529, interrupt/cancel, or other terminal reasons.Add a harness case: assistant
{ error: "rate_limit" }+ genericapi_errorresult, norate_limit_event→ named limit. Optional: also emit the #7165 warning from that assistant error; not required to close this.Web / desktop / mobile already consume
runtime.error/turn.completed.errorMessage, so one adapter change covers every client. No contract change. Sparserate_limit_eventis upstream; T3 already has the structured assistant error.- assistant
- addedbugSomething is broken or behaving incorrectly.Something is broken or behaving incorrectly.acceptedfeature request acceptedfeature request acceptedvia-triageFiled through npx t3 triageFiled through npx t3 triage
on Sep 7, 2026 Thanks. #10546 follows that shape: the assistant
rate_limiterror is latched on the current turn next to the sign-out latch (after the subagentparent_tool_use_idreturn, so subagent snapshots are untouched), the existing sentence is reused, and no other terminal reason is overridden. Harness cases: assistantrate_limit+ genericapi_errorwith norate_limit_event, two consecutive turns in one session, and aserver_errorcontrol. The #7165 warning row is left as is.
Before submitting
Area
apps/server
Steps to reproduce
Expected behavior
Every limited turn names the limit, as the first one did.
Actual behavior
Only the first limited turn gets the new sentence. Each retry ends with the old "Claude gave up after repeated API errors." above the CLI's own line "You've hit your session limit · resets 9:20pm".
The CLI sends
rate_limit_eventwithstatus: "rejected"only when the limit state changes, so the first turn records it inturnState.rejectedRateLimitTypesand the result names the limit. A retry while still limited carries no newrate_limit_event; it only carries anassistantmessage witherror: "rate_limit"(the SDK'sSDKAssistantMessageErrorunion) and thenresultwithterminal_reason: "api_error".handleAssistantMessageinapps/server/src/provider/Layers/ClaudeAdapter.tslatcheserror: "authentication_failed"from that message but noterror: "rate_limit", sohandleResultMessagehas no evidence and falls back to the generic sentence.Three turns in a row on the same thread, 19:25 correct, 19:26 and 19:26 generic:
Impact
Minor bug or occasional failure
Version or commit
main @ 1d1bf50
Environment
macOS 15, desktop app; Claude Code CLI 2.1.263, Claude subscription (OAuth); server on Linux
Logs or stack traces
Screenshots, recordings, or supporting files
No response
Workaround
None; the first turn's message is still visible above.