Skip to content

bug(codex): automatic quota activation stalls after reset #3855

Closed

Description

@terrytan95

Client or integration

Codex App

Area

Authentication and account pool

Summary

Automatic quota-window activation can remain enabled while no new window is activated after reset. Three related gaps exist in v2.45.0 and remain in the current dev implementation:

  1. A successful warmup records the completed reset but discards the response's quota headers. Without unrelated traffic or dashboard quota reads, subsequent sweeps keep seeing the completed old timestamp and do not activate the next idle window.
  2. Failed activation records only a retry delay. Re-reading quota can replace the expired reset with a future idle-window reset, so the retry no longer finds a due window. There is no retained pending boundary.
  3. A time-valid but upstream-rejected bearer is reused by the warmup path. An inference 401 neither enters authentication recovery nor marks that credential for reauthentication; failures are swallowed into another five-minute retry.

Expected: opted-in idle accounts keep scheduling successive observed windows, pending activation survives metadata changes, and rejected credentials have a bounded recovery or actionable reauthentication outcome. Failures should expose a safe diagnostic reason.

Reproduction

Enable both automatic windows for an added Codex account in canonical OpenAI Pool mode. Keep the account unpaused and run the proxy continuously.

Scenario A — consecutive idle windows:

  1. Supply an expired 5-hour quota snapshot to the activation worker.
  2. Let the minimal warmup complete, including quota headers identifying the next 5-hour reset.
  3. Produce no unrelated account traffic or dashboard quota refresh.
  4. Advance beyond the next reset and run the worker again. Only the first inference was sent; the quota cache still contains the original completed boundary.

Scenario B — lost retry:

  1. Run an enabled expired weekly window and make warmup fail, for example with 429.
  2. Before the five-minute retry, let a quota read report an idle weekly reset in the future.
  3. Run the worker after its retry delay. It no longer attempts the pending activation.

Scenario C — rejected but locally time-valid bearer:

  1. Use a fixture credential whose expiresAt is in the future.
  2. Make the Codex Responses endpoint return 401.
  3. Run the worker at the due time and five minutes later. The same bearer is sent twice, with no recovery request and needsReauth still false.

These scenarios were reproduced with the actual v2.45.0 worker/warmup code and injected upstream responses in an isolated temporary home. No real inference, reset-credit redemption, or account mutation was used for the investigation.

Separately, read-only observation of a real opted-in account returned HTTP 401 with token_revoked while its local expiry remained in the future. Another account's unused weekly window repeatedly reported reset_after_seconds=604800 and a reset_at that moved with query time. These observations support the relevant failure conditions; absent activation logs prevent claiming an exact historical sequence for every affected account.

Version

2.45.0; official tag b0900e5. The same activation implementation is present at dev 6cf38b5.

Operating system

macOS 27.0; Bun 1.4.0

Provider and model

Canonical openai provider, Codex Pool mode; default warmup gpt-5.4-mini, with the existing gpt-5.5 fallback on HTTP 400.

Logs or error output

v2.45.0 isolated observations:
first reset: warmupRequests=1; cached reset unchanged
next reset after five idle hours: warmupRequests=1
same tick after external quota refresh: warmupRequests=2

failed due request, then future weekly metadata:
attempts=1; nextTickDue=null

401 with locally time-valid credential:
inference requests=2; sameBearer=true; needsReauth=false

The worker does not currently log the caught activation failure.

Screenshots and supporting files

Source references: src/codex/quota-auto-refresh.ts (due selection, warmAccount, completion/retry), src/codex/warmup.ts (success headers and HTTP failures), src/codex/account-store.ts (expiresAt shortcut). The existing 64 focused worker/warmup/lifecycle checks passed during the v2.45.0 audit but do not cover these combined scenarios.

Redacted configuration

{
  "defaultProvider": "openai",
  "providers": {
    "openai": {
      "adapter": "openai-responses",
      "baseUrl": "https://chatgpt.com/backend-api/codex",
      "authMode": "forward",
      "codexAccountMode": "pool"
    }
  },
  "codexAccounts": [{ "id": "pool-fixture", "isMain": false, "plan": "team" }],
  "codexQuotaAutoRefresh": {
    "pool-fixture": { "fiveHour": true, "weekly": true }
  }
}

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
    account-poolOAuth, credentials, Codex pool, quota, failover, plans
    on Sep 7, 2026
  2. lidge-jun commented on Sep 7, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 72 / 80

    이슈 #3855는 Codex 풀에서 자동 쿼터 창 활성화를 켠 계정이, 리셋 직후 한 번만 워밍업하고 다음 유휴 창을 놓치거나, 실패 재시도 중에 메타데이터가 앞으로 밀리면 대기 작업이 사라지고, 로컬 expiresAt은 유효한데 업스트림이 401인 bearer를 같은 경로로 반복 보내는 문제입니다. 제보는 v2.45.0과 dev 6cf38b59a 워커/워밍업을 격리 홈에서 시나리오 A/B/C로 재현했고, 실계정에서는 token_revoked 401과 매주 reset_after_seconds=604800처럼 시계가 미끄러지는 관측도 덧붙였습니다. 지금 dev HEAD(ece556a6e, 2.47.0)에서도 같은 골격입니다. #3854는 code-mode host contract만 넣었고, quota auto-refresh 워커는 그대로입니다.

    HEAD의 src/codex/quota-auto-refresh.ts를 보면 동작이 제보와 맞습니다. dueCodexQuotaAutoRefreshWindows(약 65–93줄)는 캐시된 shortResetAt/weeklyResetAt이 now 이하이고, 설정의 lastFiveHourResetAt/lastWeeklyResetAt 및 메모리 completedByAccount와 다를 때만 due로 잡습니다. 성공하면 persistCompleted가 last*만 패치합니다(약 126–150줄). 다음에 올 경계(next)를 붙잡아 두는 필드가 없습니다.* warmAccount(약 101–123줄)는 warmCodexAccount(token)만 호출하고, 완료 응답의 쿼터 헤더를 캐시에 반영하지 않습니다. 그래서 대시보드/다른 트래픽이 쿼터를 안 읽으면 completed 마커만 남고 캐시 reset은 옛값이라, 다음 창이 due로 안 뜹니다(시나리오 A). 실패 시 retryAfterByAccount에 5분만 넣고(약 201–202줄), 그 사이 쿼터 읽기가 idle 미래 reset으로 바뀌면 due 조건이 사라져 재시도가 “할 일 없음”이 됩니다(시나리오 B). catch는 로그도 남기지 않습니다.

    시나리오 C도 HEAD와 맞습니다. src/codex/account-store.ts의 getValidCodexToken(약 717줄 근처)은 expiresAt이 skew 밖이면 저장된 bearer를 그대로 줍니다. warmCodexAccount(src/codex/warmup.ts)가 HTTP 401이면 CodexWarmupError(http_status)를 던지지만, auto-refresh의 catch는 그걸 needsReauth로 올리지 않고 5분 재시도만 합니다. 같은 시간유효 bearer가 다시 나갑니다. axis1에서 들어온 서빙 중 쿼터 귀속·리셋 마커 정규화(#3607 등)와 #3375/#3376 풀 라이프사이클 요청은 이미 풀에 있는 계정의 연속 활성화 구멍을 이 이슈만큼 좁히진 않았습니다. 구현 PR은 #3856(draft, terrytan95)입니다. types/config 분할과 무관한 account-pool 동작이라 close-don't-rebase 대상이 아닙니다.

    src/codex/quota-auto-refresh.ts:65-93 - due는 캐시 reset≤now와 last*/completed 불일치만 봄. pending next 경계 없음
    src/codex/quota-auto-refresh.ts:101-123 warmAccount - 성공 헤더를 applyAccountQuotaFromUpstreamHeaders로 안 넣음
    src/codex/quota-auto-refresh.ts:188-203 - 실패 시 retryAfter만 설정, 로그·needsReauth·pending 보존 없음
    src/codex/warmup.ts:257-265 - http_status(401 포함) throw. onCompleted 훅 없음(HEAD)
    src/codex/account-store.ts:717+ getValidCodexToken - expiresAt이 미래면 거부된 bearer 재사용 가능
    관련 #3375/#3376/#3846/#3848 - 풀 라이프사이클·등록 게이트와 같은 축이나, “연속 idle 창 활성화”는 이 이슈가 더 구체적

    메인테이너의 판단이 필요한 지점

    • nextFiveHourResetAt/nextWeeklyResetAt 같은 설정 필드 추가로 pending을 영속화할지, 메모리+재시작 유실을 감수할지
    • 성공 워밍업 헤더를 즉시 쿼터 캐시에 반영하는 것을 기본으로 할지(권장: opted-in 계정만)
    • 추론 401을 needsReauth로 올릴지, WHAM recovery만 태울지, 둘 다할지
    • #3856을 이 이슈의 정식 픽스로 보고 auth-api 변경에 maintainer-sponsored를 줄지

    너의 추천
    이슈는 열어 둔다. 라벨(bug, account-pool) 유지. 수정은 #3856 궤도로: pending 경계 보존, 성공 헤더 반영, opted-in stale 메타데이터 주기 refresh, 401→reauth/진단 로그. 회귀 테스트는 제보 A/B/C를 최소 고정 테스트로 남긴다. #3848(등록 시 validation pending)과 한 PR로 묶지 말고, “이미 등록·opt-in된 계정의 연속 활성화”만 닫는다.

    이 댓글은 grok-bot이 작성했습니다

  3. lidge-jun commented on Sep 7, 2026

    @lidge-jun
    Owner

    Fixed on dev by #3871 (merge 62fe747af), carrying @terrytan95's #3856: quota-window activation now retains its pending boundary across idle windows, publishes warmup quota headers, and handles a rejected bearer without stalling. Ships in the next release.

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