CLI surfaces transient 429 "rate_limit_error" to user with no backoff/retry; session sits idle until user types `continue`
Summary
When the Anthropic API returns a transient 429 rate_limit_error ("Server is temporarily limiting requests (not your usage limit) · Rate limited"), Claude Code v2.1.x writes a synthetic assistant message tagged isApiErrorMessage:true to the JSONL and stops. There is no automatic backoff or retry — the session sits idle until the user manually types continue. The same transient typically clears within a few seconds; a small jittered exponential backoff on the CLI side would resolve nearly all of these without user intervention.
Environment
- Claude Code CLI v2.1.145 (also confirmed on v2.1.149)
- Linux (Debian 12 cloud VM)
- Direct Anthropic API (Max plan OAuth, not Bedrock/Vertex/Foundry)
What we observed
Running ~25 concurrent Claude Code sessions against one API key for a fan-out workflow, two cluster-wide 429 spikes today:
- 17:35 UTC — 26 sessions hit a 429 inside the same minute
- 15:17 UTC — 25 sessions hit a 429 inside the same minute
Total of 55 × 429s across 50 sessions over 3 hours. Every one of them was the synthetic-message-then-stop shape, and every one would have cleared with a few seconds of backoff (we recovered each by typing continue 5–10 s later and the next request succeeded).
Reproduction
Easiest repro: run enough concurrent Claude Code sessions on one API key to saturate the per-key shared rate limit. The "Server is temporarily limiting requests (not your usage limit)" wording is the server-side throttle, distinct from the account-side Usage credits required 429s reported in other issues (#22876, #60438, #62314).
Evidence — the synthetic error message
From an affected session's JSONL transcript (~/.claude/projects/<workspace-slug>/<session-id>.jsonl), the assistant entry following a successful tool_result:
{
"type": "assistant",
"isApiErrorMessage": true,
"apiErrorStatus": 429,
"error": "rate_limit",
"message": {
"model": "<synthetic>",
"stop_reason": "stop_sequence",
"content": [{
"type": "text",
"text": "API Error: Server is temporarily limiting requests (not your usage limit) · Rate limited"
}]
},
"requestId": "req_011Cb...",
"userType": "external",
"entrypoint": "cli",
"version": "2.1.145"
}
In our case the session then sat idle for 6 min 18 s until the user typed continue, after which inference resumed and the next request succeeded immediately. That entire 6+ minute window was avoidable.
What we'd hope for
Full-jitter exponential backoff (per the AWS 2015 "Exponential Backoff And Jitter" formula) on the CLI's HTTP client when the response is error.type === \"rate_limit_error\" or \"overloaded_error\":
sleep = uniform(0, min(cap, base * 2**attempt))
Suggested defaults: base=1.0s, cap=30.0s, max_retries=6 (≈64 s upper bound on total backoff budget). Honor retry-after if Anthropic ships it as a response header. After exhausting retries, fall through to the current synthetic-message-then-stop behavior.
This is the same shape the official Anthropic Python SDK uses internally via its max_retries constructor parameter, and matches what we've had to ship as a Stop-hook wrapper on our side to bridge the gap. We'd much rather it lived in the CLI.
Local mitigation
For anyone hitting this in the meantime: a Claude Code Stop hook can read the transcript tail, detect isApiErrorMessage:true, sleep with exp backoff, and exit 2 to make the harness retry. The retry attempt count is recoverable from the JSONL itself (count of consecutive trailing isApiErrorMessage entries), so multi-retry behavior survives stop_hook_active=true. We shipped one as retry-on-api-error.py — happy to share the source if it'd help.
Why this isn't the same as the other 429 issues
- #22876 / #40128 / #61808 / #62314: account-side 429s on accounts with available quota — those are a billing/quota classification bug, distinct shape.
- #60438: a persistent 429 from auto-mode classification — not transient.
- #60967 (closed): UX request to suggest starting a new conversation. Related but doesn't fix the wait.
- #47931: Windows-specific silent termination after parallel tool_results, no error written.
This issue is specifically: the CLI sees a clearly-retryable transient 429 rate_limit_error response, has all the information it needs to back off and retry, and instead just stops.
Test plan
- [ ] Confirm the CLI does in fact have no retry path for
rate_limit_error(search HTTP client retry logic). - [ ] Add jittered exp backoff with the defaults above (or whatever Anthropic prefers).
- [ ] Honor
retry-afterif set. - [ ] Fall through to the current synthetic-message-then-stop only after the budget is exhausted.
- [ ] Test under a forced 429 (e.g. against a local mock returning 429 N times before 200) that the session recovers without user intervention.
🤖 Filed by Claude Code on behalf of a multi-session operator.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗