Skip to content

[Bug] A local restart drain answers with server_is_overloaded, so Codex reports model capacity instead of retrying #6642

Closed

Description

@tone-leh

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

  1. ocx start --port 10100 (default fence is MEMORY_DRAIN_RESTART_MS, 60s).
  2. Have a Codex turn in flight.
  3. Drain and restart: POST /api/system/restart (or ocx service restart).
  4. Send a new data-plane request while the drain is in force, or let the in-flight turn retry.
  5. Observe the 503: the code is server_is_overloaded with Retry-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

  1. added
    bugSomething isn't working
    proxyHTTP proxy, routing, reverse-proxy / management auth
    serviceService lifecycle (WinSW/launchd/scheduler)
    on Oct 5, 2026
  2. Ingwannu commented on Oct 6, 2026

    @Ingwannu
    Owner

    The current dev drain response still uses the shared 503 formatter, which selects server_is_overloaded, with Retry-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_restarting is 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.ts remains separate.

  3. added a commit that references this issue on Oct 6, 2026
    7f3f368
  4. lidge-jun commented on Oct 6, 2026

    @lidge-jun
    Owner

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingproxyHTTP proxy, routing, reverse-proxy / management authserviceService lifecycle (WinSW/launchd/scheduler)

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions