Repository navigation
Description
What version of Codex CLI is running?
Source review against main at bdd282f3bbd55df3a869a5438519cd948c134d4d (2026-06-27).
What subscription do you have?
N/A - source-review / reliability issue.
Which model were you using?
N/A.
What platform is your computer?
N/A.
What issue are you seeing?
There are many user reports of errors like:
exceeded retry limit, last status: 429 Too Many Requests, request id: ...
After tracing the current source, this message appears misleading for at least one common path: a direct HTTP 429 from the Responses HTTP/WebSocket wrapper does not appear to be retried by the local request retry policy, because retry_429 is hard-coded to false for providers.
Relevant code:
// codex-rs/model-provider-info/src/lib.rs
let retry = ApiRetryConfig {
max_attempts: self.request_max_retries(),
base_delay: Duration::from_millis(200),
retry_429: false,
retry_5xx: true,
retry_transport: true,
};The retry machinery does support 429 in principle:
// codex-rs/codex-client/src/retry.rs
TransportError::Http { status, .. } => {
(self.retry_429 && status.as_u16() == 429)
|| (self.retry_5xx && status.is_server_error())
}But since providers set retry_429: false, a direct HTTP 429 falls through and is mapped here:
// codex-rs/codex-api/src/api_bridge.rs
} else if status == http::StatusCode::TOO_MANY_REQUESTS {
...
CodexErr::RetryLimit(RetryLimitReachedError {
status,
request_id: extract_request_tracking_id(headers.as_ref()),
})
}The displayed text then comes from:
// codex-rs/protocol/src/error.rs
"exceeded retry limit, last status: {}{}"And CodexErr::RetryLimit(_) is explicitly non-retryable in CodexErr::is_retryable(), so the turn-level stream retry loop will not retry it either.
This creates two problems:
- The user-facing message says
exceeded retry limit, but in this path the client may not have retried the HTTP 429 at all. request_max_retriesis user-configurable, but users cannot make HTTP 429 retryable becauseretry_429is not exposed inconfig.toml/ModelProviderInfo.
This is distinct from SSE response.failed rate-limit events. Those can become ApiError::Retryable and use the stream retry path. The confusing behavior here is for direct HTTP 429 responses from the transport/wrapper layer.
What steps can reproduce the bug?
Source-level reproduction / audit:
- Configure a provider normally, with default
request_max_retries. - Have the Responses endpoint return an HTTP 429 before a successful SSE stream is established.
- Observe that provider retry config has
retry_429: false. - The request is mapped to
CodexErr::RetryLimitand displayed asexceeded retry limit, last status: 429 Too Many Requests. CodexErr::RetryLimitis non-retryable, so the turn fails instead of entering the stream retry/backoff path.
What is the expected behavior?
At minimum, the behavior and message should be aligned:
- If HTTP 429 is not retried by design, do not call it
exceeded retry limit; display a clearer message such asrate limited: HTTP 429 Too Many Requestsand include any useful reset/retry-after information if available. - If HTTP 429 is intended to be retryable, set
retry_429: truefor appropriate providers or expose it as a provider config option.
A possible config shape:
[model_providers.openai]
request_max_retries = 8
retry_429 = truePotential implementation points:
- Add
retry_429: Option<bool>toModelProviderInfo. - Add it to
core/config.schema.json. - In
to_api_provider(), use something likeretry_429: self.retry_429.unwrap_or(false)or a provider-specific default. - Consider honoring
Retry-Afterheaders for transport-level 429, not only delay text parsed from SSEresponse.failedmessages. - Consider renaming or splitting
RetryLimitReachedErrorso direct 429s are not mislabeled as retry exhaustion.
Additional information
Related user reports include many issues with this exact text, for example:
- Got "exceeded retry limit, last status: 429 Too Many Requests" #11434
- CLI: remote compact task fails with retry exhaustion and 429 Too Many Requests #15468
- exceeded retry limit, last status #18540
- exceeded retry limit, last status: 429 Too Many Requests, request id: 9f9fe320aa92b10b-FRA #22122
- getting
exceeded retry limit, last status: 429 Too Many Requestsover and over again, no recent changes to any configs #23304 - exceeded retry limit, last status: 429 Too Many Requests, request id: c2d7a2ba-a723-411f-a8f8-b60059d8e4c9 #24657
- exceeded retry limit, last status: 429 Too Many Requests #26034
- exceeded retry limit, last status: 429 Too Many Requests #26655
In #26034, a maintainer noted an active incident and said it was not client-side. This issue is not disputing that some 429s are service-side incidents. It is specifically about the local client behavior and wording when a transport-level HTTP 429 is received.
Activity
wgu9 commentedon Jun 29, 2026
Current code already has the pieces to make this clearer, but the behavior is ambiguous.
In
codex-rs/codex-api/src/provider.rs, retry policy has an explicitretry_429flag. Several endpoint configs set it tofalse, including realtime/search/images/models paths. Incodex-rs/codex-api/src/api_bridge.rs, HTTP 429 is only mapped toUsageLimitReachedwhen the body matches the Codex usage-limit shape; otherwise it falls through to a more generic transport/API error path.So there are two separate cases that should probably have different user-facing messages:
UsageLimitReachedas todayretry_429=false: do not say or imply that a retry limit was exhaustedSuggested fix boundary:
retry_429=falsefor endpoints where automatic retry is not wantedHTTP 429 received; this endpoint is configured not to retry 429 automaticallyRetry-Afterheader was presentretry_429=falseto prevent the misleading “exceeded retry limit” wordingfeat(proxy): opt-in same-target 429 wait-and-retry before key failover (
feat(proxy): opt-in same-target 429 wait-and-retry before key failover (
callmemorgan commentedon Sep 24, 2026
+1 on this issue. Reading through the code, this looks like either an over-optimization against OpenAI's first party APIs, or a bug. I would expect 429s should be retryable, especially if the underlying service provider supplies a
Retry-After:header.The rest that follows is LLM-generate (possible slop):
Note
GPT-6 in Codex, posting on behalf of Morgan.
We're hitting this too. We're configured against an API that frequently returns HTTP 429, and want to use Codex for long-running jobs with no fixed completion deadline. Waiting for capacity is acceptable; having to manually restart the turn after each temporary rejection is not.
The same behavior is still present in
12fd929f724371104a1dea7c14930b078d6b4a18:retry_429: false.CodexErr::RetryLimit.retry_delay()treatsRetryLimitas terminal, so the outer retry loop cannot recover either. Increasing the configured retry counts doesn't change this classification.One clarification about
Retry-After: the HTTP transport already parses it, and the API error mapper preserves it. The terminal classification prevents that advice from being used for these 429s.We'd like an opt-in policy that retries temporary capacity/rate-limit responses for as long as the job remains active, honors
Retry-After, and otherwise uses exponential backoff with a capped delay and jitter. It should remain cancellable and keep genuine quota/billing failures terminal. The existing retry machinery appears to cover much of this; the 429 classification is the immediate blocker.WhiteWarrior625 commentedon Oct 26, 2026
Submitted by Codex on behalf of @WhiteWarrior625.
Confirmed a related diagnostic problem in Codex CLI 0.164.0, using ChatGPT Pro,
gpt-6.1-sol, Standard service, through CLIProxyAPI 8.0.38 on Linux. This is an observed incident plus a source trace, not a deterministic reproduction of OpenAI capacity rejection.On October 26 at approximately 13:54 UTC, the inspected proxy response was HTTP 429 with:
{"error":{"code":"turn_rate_limit_exceeded","message":"Codex is at capacity, please try again later","type":"rate_limit_error"}}The app-server consumer received the generic
responseTooManyFailedAttemptserror andexceeded retry limit, last status: 429 Too Many Requests. The detailed capacity explanation was absent from the persisted failure.The authenticated account check reported
ordinaryUsageAllowed=trueand 2% weekly usage. That establishes remaining weekly allowance, but does not rule out a separate request, model or concurrency restriction. A subsequent native Codex request through the same provider configuration completed successfully.The released 0.164.0 source explains the lost detail:
RetryLimitReachedErrorcontaining only status and request ID.Please preserve a safe structured upstream error code/message, or expose recognized capacity rejection distinctly, while preserving genuine quota/reset behavior. When the cause is unknown, neutral HTTP 429 wording would be more accurate than suggesting a known exhausted allowance.
This affects downstream clients too: T3 Code #24885 shows the resulting misleading usage-limit explanation. Our bounded proxy retries mitigate brief refusals; they do not repair the lost diagnostic information in Codex.
fix: make HTTP 429 retries and request backoff configurable