Repository navigation
[Bug] A local restart drain answers with server_is_overloaded, so Codex reports model capacity instead of retrying #6642
Description
Client or integration
Codex CLI
Area
Proxy and routing
Summary
When OpenCodex is draining for a restart, every new data-plane request is answered by drainingResponse in src/server/index.ts with formatErrorResponse(503, "server_error", "Service shutting down"). Upstream's own comment above that function states the payload carries the server_is_overloaded code so clients can back off.
Codex does not treat that code as a retryable backoff. It reads server_is_overloaded as a capacity refusal: the turn ends with "Selected model is at capacity. Please try a different model." and its sampling retries are skipped. So a restart that lasts a few seconds costs the whole turn, and the message points the user at the model picker for something that has nothing to do with model capacity.
A local drain is a restart notice. It should carry a retryable code so the client's own retry can outlast the handoff.
Reproduction
ocx start --port 10100(default fence isMEMORY_DRAIN_RESTART_MS, 60s).- Have a Codex turn in flight.
- Drain and restart:
POST /api/system/restart(orocx service restart). - Send a new data-plane request while the drain is in force, or let the in-flight turn retry.
- Observe the 503: the code is
server_is_overloadedwithRetry-After: 5, and Codex reports a capacity refusal instead of retrying.
The refusal is emitted before routing, so no provider is contacted and no credential is spent.
Version
2.78.0
Operating system
macOS 27.0.1 (arm64). Not platform-specific - the response is built in shared proxy code.
Provider and model
Not provider-specific.
Logs or error output
From the source rather than a captured run: src/server/index.ts:473-481 builds the body with formatErrorResponse(503, "server_error", "Service shutting down"), whose code is server_is_overloaded per the comment immediately above the function.
Suggested change
Return a JSON envelope that keeps 503 and Retry-After but carries a retryable code:
function drainingResponse(req: Request, policy: RequestPolicyView): Response {
const headers = new Headers({ "Content-Type": "application/json" });
for (const [name, value] of Object.entries(corsHeaders(req, policy))) {
headers.set(name, value);
}
headers.set("Retry-After", "5");
return new Response(JSON.stringify({
error: {
message: "OpenCodex is restarting; retry this request.",
type: "server_error",
code: "server_restarting",
},
}), { status: 503, headers });
}This is what we run as a local patch. Happy to open a PR if you would rather review it that way.
Related: the drain fence duration is a second, separate half of the same handoff problem - I will file it separately and cross-reference it here.
Screenshots and supporting files
None.
Redacted configuration
Not configuration-dependent.
Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
Activity
Ingwannu commentedon Oct 6, 2026
The current dev drain response still uses the shared 503 formatter, which selects
server_is_overloaded, withRetry-After: 5. That source path is confirmed. The claim that this code suppresses retries is client-version dependent: the current formatter/dispatch comments describe it as a retryable backoff shape, so comments alone cannot settle what your installed Codex does.Please add the exact Codex version and a redacted captured local 503 envelope together with retry timestamps (or the relevant client error-classification source). A focused PR should distinguish planned local restart from provider capacity while preserving listener-policy CORS, HTTP 503, refusal before upstream dispatch, and existing retry/backoff controls. Please include a client-classification regression for the proposed code rather than assuming
server_restartingis recognized.Treat this separately from the fence-duration issue #6643. No restart or live client test was performed here; the required owner review of
src/server/index.tsremains separate.fix(lifecycle): retryable restart-drain code and quiet journal no-op …
lidge-jun commentedon Oct 6, 2026
Fixed on dev by #6686 (merge 7f3f368): a restart drain now answers 503 with Retry-After: 5 and the retryable server_restarting code instead of server_is_overloaded, so Codex retries instead of reporting model capacity. The 60 s drain length from #6643 is unchanged. Ships with the next release cut from dev.