Skip to content

[Bug]: quota routing replaces healthy main pin with a weekly-exhausted pool account in 2.39.0 #3157

Description

@y2ambition-ai

Client or integration

Codex CLI (the same Pool state is visible in the OpenCodex dashboard)

Area

Authentication and account pool

Summary

On OpenCodex 2.39.0, quota routing can deterministically override a healthy manually selected main account and persistently switch to a pool account whose weekly quota is already known to be 100% exhausted.

This is not the short-only/unknown-score case fixed by #3029 / #3110. In this reproduction:

  • main account: weeklyPercent: 16
  • stored pool account: weeklyPercent: 100
  • strategy: quota
  • auto-switch threshold: 90
  • both account selection priorities: 0
  • both accounts are confirmed entitled to gpt-5.6-sol

After manually selecting main, OpenCodex reports activeId=__main__ and persists activeCodexAccountPinned=__main__. The first minimal request removes that pin, persists the exhausted stored account as active, routes the request to it, and returns a synthetic 502 with The usage limit has been reached.

I expected the known-100% account to be excluded from quota selection and the healthy 16% main account to remain selected.

Reproduction

  1. Configure the native main Codex login plus one stored Codex pool account.

  2. Use equal selection order for both accounts (0 / normal).

  3. Configure quota routing:

    ocx account strategy openai quota
    ocx account auto-switch openai threshold 90
  4. Ensure the stored account is resumed, then refresh quota:

    ocx account resume openai <pool-account-id>
    ocx account refresh openai --json
  5. Confirm the response shows:

    • __main__.quota.weeklyPercent = 16
    • <pool>.quota.weeklyPercent = 100
    • neither account paused
    • both priorities 0
  6. Select main:

    ocx account use openai main --json
  7. Confirm before the request:

    • ocx account current openai --json reports activeId: __main__
    • ocx config show reports both activeCodexAccountPinned: __main__ and activeCodexAccountId: __main__
  8. Send one minimal request:

    codex exec --skip-git-repo-check --sandbox read-only \
      --model openai/gpt-5.6-sol 'Reply with exactly: ROUTE_PROBE_OK'
  9. Observe five reconnects followed by adapter_eof / incomplete response.

  10. Read state again:

    • ocx account current openai --json now reports the exhausted stored account as active
    • activeCodexAccountPinned is gone
    • activeCodexAccountId is persisted as the exhausted stored account
  11. ocx observe logs --limit 1 --json shows the request was served by the stored account and failed with the quota message below.

The reproduction was repeated after resetting both priorities to 0; the same result occurs. Pausing the exhausted account and selecting main makes the same minimal request succeed immediately.

Expected behavior

With strategy=quota, threshold 90, a known 16% main account, and a known 100% stored account:

  • the stored account should not be selected;
  • a manual main pin with headroom should not be replaced by the exhausted account;
  • existing and new requests should route through main;
  • if an in-stream usage-limit terminal cannot safely retry the same request, it should at least make the exhausted account ineligible for the next request.

Actual behavior

The first request after selecting main automatically clears the healthy main pin, persists the known-exhausted stored account as active, and routes to it. Subsequent requests remain locked to that account and repeatedly fail.

Version

OpenCodex 2.39.0 (npm latest at reproduction time)

Codex CLI 0.150.1

Bundled Bun 1.4.0

Operating system

macOS 26.6.2 (25G83), Apple Silicon arm64

Provider and model

OpenAI Codex login account pool / gpt-5.6-sol

Logs or error output

Before request:
  strategy: quota
  auto-switch: on (threshold 90%)
  priorities: main=0, pool=0
  quota: main weekly=16%, pool weekly=100%
  activeId: __main__
  activeCodexAccountPinned: __main__
  activeCodexAccountId: __main__

Request result:
  Reconnecting... 1/5
  ...
  Reconnecting... 5/5
  stream disconnected before completion: Incomplete response returned, reason: adapter_eof

OpenCodex request log:
  status: 502
  accountLogLabel: <stored-pool-account>
  model: gpt-5.6-sol
  errorCode: upstream_server_error
  terminalStatus: incomplete
  upstreamError: The usage limit has been reached

After request:
  activeId: <stored-pool-account>
  activeCodexAccountPinned: absent
  activeCodexAccountId: <stored-pool-account>

Network and auth checks were healthy during reproduction:

ocx health
# Proxy healthy

ocx doctor
# WHAM status=200 authenticated
# OAuth reliability checks OK
# model catalog current

Redacted configuration

{
  "providers": {
    "openai": {
      "adapter": "openai-responses",
      "codexAccountMode": "pool",
      "authMode": "forward"
    }
  },
  "accountPoolStrategy": "quota",
  "autoSwitchThreshold": 90,
  "upstreamFailoverThreshold": 5,
  "codexAccountPickerEnabled": false,
  "websockets": false
}

Relation to #3029

#3029 was closed by #3110 / commit 42ad9c44d, which makes a live short-only 100% window score as exhausted instead of unknown. This reproduction is on 2.39.0 after that fix and has a known long-window value: the stored account has weeklyPercent=100 and main has weeklyPercent=16. Therefore the exhausted account should already score 100 through the ordinary long-window path.

The in-stream usage terminal is still surfaced as synthetic 502 rather than pre-stream 429/402, so same-request quota retry does not run. However, the more surprising part is that the routing decision actively moves away from a healthy pinned main account and persists the known-100% stored account before producing that failure.

Workaround

Disable auto-switch, pause the exhausted account, and manually select main:

ocx account auto-switch openai off
ocx account pause openai <pool-account-id>
ocx account use openai main

This keeps the stored account and credentials intact and makes the minimal request succeed.

Checks

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

Activity

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

    account-poolOAuth, credentials, Codex pool, quota, failover, plansbugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions