Skip to content

Native-profile lock deadlock: all native requests 503 with zero profiles registered, and account main recover cannot clear it #1120

Description

@MarcusNeufeldt

Client or integration

Codex CLI

Area

Authentication and account pool

Summary

The native-main-profile lock can enter a state where it is never released, and every native-routed request then fails with 503 server_is_overloaded / "Native Codex main profile is switching; retry this request".

The lock is held even when no native profiles are registered at all (profiles: [], activeProfileId: null, and an empty <CODEX_HOME>/.opencodex-native-main-profiles/ directory). The subsystem gates all traffic for a feature that has nothing configured in it.

The documented recovery command, ocx account main recover, cannot clear it: that command is a client of the proxy's management API, and the management API refuses while the lock is held. Stopping the proxy releases the lock at the filesystem level, but then every management command fails with Proxy is not running. There is no out-of-band break-glass path, so the state is unrecoverable through supported commands.

I expected either that the lock would not be taken when no profiles exist, or that ocx account main recover would be able to clear it.

Secondary usability problem: returning code: server_is_overloaded makes the Codex CLI render "Selected model is at capacity. Please try a different model." That points users at OpenAI quota exhaustion. It took a fair amount of digging to establish the request never left the machine — durationMs of 27-45ms and usageStatus: "unreported" were the giveaways.

Reproduction

Not consistently reproducible on demand; it appeared spontaneously and then recurred in bursts. What I observed:

  1. ocx start with codexAccountMode: pool, a single Codex account (main, the Codex App login), and no native main profiles registered.
  2. Send any request through the proxy to a native model, e.g. POST http://127.0.0.1:10100/v1/responses with {"model":"gpt-5.4-mini", ...}.
  3. Observe 503 returned in single-digit milliseconds with Native Codex main profile is switching.
  4. Attempt the documented recovery — it fails on the same lock:
$ ocx account main doctor --json
Error: The native-profile lock is unavailable.

$ ocx account main recover --json
Error: The native-profile lock is unavailable.

$ ocx account main list --json
{"effectiveCodexHome":"<CODEX_HOME>","activeProfileId":null,"profiles":[]}
  1. ocx restart clears the currently active burst but does not prevent recurrence.

Observed burst pattern over ~3 hours, across a proxy restart. Note the bimodal durations (0-5s vs 211-268s), which look like lock contention rather than a crash:

start     end       dur    gap-since-prev-start
06:42:15  06:46:43  268s   -
06:49:04  06:49:07    3s   7m
06:59:20  07:00:37   77s   10m
07:07:48  07:07:51    3s   8m
07:12:55  07:14:22   88s   5m
07:16:34  07:16:52   18s   4m
07:32:43  07:32:58   15s   16m
07:43:55  07:43:58    3s   11m
08:22:51  08:22:54    3s   39m
08:33:04  08:33:09    5s   10m
08:36:08  08:36:38   30s   3m
08:41:16  08:41:16    0s   5m
   --- ocx restart here (08:43) ---
09:38:06  09:41:36  211s   57m
09:44:31  09:44:31    0s   6m

One correlation worth checking: a codex-quota-cache.json write landed at 09:38:19, 13 seconds after that burst began, which suggests the quota-refresh path takes the same lock.

Workaround that resolved it for me: ocx provider account-mode direct. With a single Codex account, pool mode adds nothing, and direct mode keeps the profile-switching layer out of the request path. Verified working end-to-end afterwards; no recurrence since.

Note the catch-22 in applying it: setting account-mode requires the proxy to be running, while clearing the lock requires it to be stopped. The window to apply the workaround is whatever clean interval happens to occur.

Suggested fixes

  1. Do not acquire the native-profile lock when no profiles are registered. If profiles is empty and activeProfileId is null, the subsystem should be inert.
  2. Give recover an out-of-band path that does not depend on the proxy's management API, e.g. an offline --force operating on the lock file directly while the proxy is stopped. A recovery tool gated behind the thing it recovers cannot function.
  3. Add a lock lease/timeout so a holder that never completes releases automatically.
  4. Use a distinct error code instead of server_is_overloaded, or name opencodex in the message, so users are not sent to investigate OpenAI quota.
  5. Surface this in ocx doctor, which reported OAuth health: ok while all traffic was failing.

Candidate source locations from code search: src/codex/native-profile-manager.ts, src/codex/native-profile-store.ts, src/codex/native-main-owner.ts, src/codex/native-main-claim.ts, src/codex/native-profile-startup.ts, src/server/management-api.ts, src/cli/doctor.ts.

Possibly a regression from the native-main-login work in #656.

Version

2.10.1 (also inspected dev at v2.10.2; no native-profile changes in that release, so this should still reproduce)

Operating system

Windows 11 Pro 10.0.26200

Provider and model

openai (native Codex) / gpt-5.4-mini, gpt-5.6-luna, gpt-5.6-terra — all native-family models failed identically

Logs or error output

# usage.jsonl record for a failing request
{
  "provider": "openai",
  "model": "gpt-5.4-mini",
  "admissionKind": "loopback",
  "inboundProtocol": "responses",
  "status": 503,
  "durationMs": 32,
  "usageStatus": "unreported",
  "errorCode": "server_is_overloaded",
  "upstreamError": "Native Codex main profile is switching; retry this request",
  "routeDecision": { "routeKind": "native" }
}

# reproduced directly against the proxy
POST http://127.0.0.1:10100/v1/responses
-> HTTP 503 in 0.0068s
{"error":{"message":"Native Codex main profile is switching; retry this request",
          "type":"server_error","code":"server_is_overloaded"}}

# recovery blocked by the same lock
$ ocx account main doctor --json
Error: The native-profile lock is unavailable.

$ ocx account main recover --json
Error: The native-profile lock is unavailable.

# nothing registered in the subsystem holding the lock
$ ocx account main list --json
{"effectiveCodexHome":"<CODEX_HOME>","activeProfileId":null,"profiles":[]}

# lock file present and actively touched
<CODEX_HOME>/.opencodex-native-profile.lock.sqlite   8192 bytes
<CODEX_HOME>/.opencodex-native-main-profiles/        (empty)

Screenshots and supporting files

No response

Redacted configuration

{
  "providers": {
    "openai": {
      "adapter": "openai-responses",
      "baseUrl": "https://chatgpt.com/backend-api/codex",
      "codexAccountMode": "pool",
      "authMode": "forward"
    }
  },
  "defaultProvider": "openrouter",
  "openaiProviderTierVersion": 2,
  "activeCodexAccountId": "__main__"
}

Account pool state at the time: a single Codex account (main, Codex App login). auto-switch: on (threshold 80%) with actual weekly usage around 47%, so the threshold was never approached.

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Metadata

Metadata

Assignees

No one assigned

    Labels

    account-poolOAuth, credentials, Codex pool, quota, failover, plansbugSomething isn't workingproxyHTTP proxy, routing, reverse-proxy / management auth

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions