[Bug + Solution] Python SDK: overload retry gives up on the "retry later" response
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.5k
- PR merge metrics
- PR metrics pending
Description
What issue are you seeing?
I've found that the Python SDK doesn't retry the app-server's Server overloaded; retry later. response, even when using its overload-retry helper with max_attempts=3. It stops after the first request and raises CodexRpcError.
I've checked both CodexClient.request_with_retry_on_overload() and its async version, and they behave the same way. This is the error they don't recognise:
{"code": -32001, "message": "Server overloaded; retry later."}
For example, let's consider a service that sends several requests to Codex and uses this helper to handle temporary overload. If the app-server is briefly too busy to accept a request, I would expect the helper to wait and try again. However, with this response, the operation fails immediately and configuring retries doesn't help.
I reproduced this using the complete SDK and a local subprocess returning the same error as the native app-server. I also checked what happens if the test server adds data: {"codex_error_info": "server_overloaded"} to that response. In this case, the SDK recognises the metadata, raises ServerBusyError, and the helper retries successfully. So the retry logic works, but it relies on metadata that the native queue-overload response doesn't include.
What steps can reproduce the bug?
To make this easier to check, here is a small example. Run this from the Codex repository root in a Bash-compatible shell, using a Python environment with the SDK dependencies installed:
PYTHONPATH=sdk/python/src python - <<'PY'
from openai_codex.errors import map_jsonrpc_error, is_retryable_error
from openai_codex.retry import retry_on_overload
# Matches the queue-overload response in
# codex-rs/app-server-transport/src/transport/mod.rs
OVERLOADED_ERROR_CODE = -32001
OVERLOADED_ERROR_MESSAGE = "Server overloaded; retry later."
calls = 0
def operation():
global calls
calls += 1
if calls == 1:
raise map_jsonrpc_error(
OVERLOADED_ERROR_CODE,
OVERLOADED_ERROR_MESSAGE,
)
return "ok"
try:
print(retry_on_overload(operation, max_attempts=3))
except Exception as exc:
print(type(exc).__name__, is_retryable_error(exc), calls)
PY
Actual output: CodexRpcError False 1.
I would expect ok after two calls, since the operation is set up to succeed on the second one, but the helper stops after the first error.
What is the expected behavior?
I would expect the helper to recognise that Codex couldn't accept the request into its queue, and retry up to the configured limit. This should also work when the optional error data is missing or null.
At the same time, other errors shouldn't automatically trigger a retry. For example, if the connection closes without a response, the server may already have accepted the request, so retrying could duplicate the operation.
Additional information
Environment and versions
I reproduced this using the Python SDK from main (revision 3d5b66c655c1).
I've also checked the relevant SDK and server code at the current Codex CLI prerelease 0.156.0-alpha.9 (20 September 2026), and it is unchanged from the tested checkout.
I used Python 3.14.4 on NixOS 26.05, x86_64 Linux. These tests didn't use a model, account, or native binary.
Root cause analysis
I've traced this to a mismatch between the native server's overload response and the SDK's error classification:
- When the incoming queue is full, the server constructs an error response with code
-32001, messageServer overloaded; retry later., and no error data. - The SDK passes this response to
map_jsonrpc_error(). For code-32001, the mapper checks for overload metadata indataor a message containing"retry limit"or"too many failed attempts". The native response has nodata, and its message,"Server overloaded; retry later.", matches neither phrase. So the mapper falls back to a genericCodexRpcError. retry_on_overload()callsis_retryable_error(). The exception is neither aServerBusyErrornor aJsonRpcErrorcarrying overload metadata, so that check returnsFalse. The helper immediately raises the exception without retrying, regardless of the configuredmax_attempts.
This also explains the working comparison. With data: {"codex_error_info": "server_overloaded"}, the mapper returns ServerBusyError in step 2, so the retry check in step 3 passes and the second request succeeds.
Suggested fix
The easiest fix appears to be adding data: {"codex_error_info": "server_overloaded"} to the native queue-overload response. The SDK already recognises this metadata and retries correctly.
However, this wouldn't help clients connecting to older servers, which still omit the metadata. To support those responses as well, I'd suggest adding a compatibility check in map_jsonrpc_error(): recognise code -32001 together with the exact message "Server overloaded; retry later." when data is missing or null, and return ServerBusyError.
I'd avoid checking the error code alone, because Codex forwards MCP errors with their original codes. Matching both code and message would narrow the compatibility check to the known native response, although it still couldn't distinguish an MCP server returning exactly the same pair.
Related issues
#19366 contains VS Code extension logs showing the same -32001 response without error data. Our reproduction uses that response to demonstrate a separate problem: the Python SDK's overload-retry helper doesn't retry it.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with sdk/python/src/openai_codex/errors.py and sdk/python/src/openai_codex/retry.py, then compare the response constructed in codex-rs/app-server-transport/src/transport/mod.rs. Run the provided Python reproduction from the repository root and verify that the native -32001 response with its exact message is retried and succeeds on the second call, while unrelated errors are not retried.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python, rust
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100