Repository navigation
bug(codex): automatic quota activation stalls after reset #3855
Description
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:
- 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.
- 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.
- 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:
- Supply an expired 5-hour quota snapshot to the activation worker.
- Let the minimal warmup complete, including quota headers identifying the next 5-hour reset.
- Produce no unrelated account traffic or dashboard quota refresh.
- 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:
- Run an enabled expired weekly window and make warmup fail, for example with 429.
- Before the five-minute retry, let a quota read report an idle weekly reset in the future.
- Run the worker after its retry delay. It no longer attempts the pending activation.
Scenario C — rejected but locally time-valid bearer:
- Use a fixture credential whose expiresAt is in the future.
- Make the Codex Responses endpoint return 401.
- 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
lidge-jun commentedon Sep 7, 2026
리뷰 · 우선순위 72 / 80
이슈 #3855는 Codex 풀에서 자동 쿼터 창 활성화를 켠 계정이, 리셋 직후 한 번만 워밍업하고 다음 유휴 창을 놓치거나, 실패 재시도 중에 메타데이터가 앞으로 밀리면 대기 작업이 사라지고, 로컬
expiresAt은 유효한데 업스트림이 401인 bearer를 같은 경로로 반복 보내는 문제입니다. 제보는 v2.45.0과dev6cf38b59a워커/워밍업을 격리 홈에서 시나리오 A/B/C로 재현했고, 실계정에서는 token_revoked 401과 매주reset_after_seconds=604800처럼 시계가 미끄러지는 관측도 덧붙였습니다. 지금devHEAD(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 창 활성화”는 이 이슈가 더 구체적
메인테이너의 판단이 필요한 지점
너의 추천
이슈는 열어 둔다. 라벨(
bug,account-pool) 유지. 수정은 #3856 궤도로: pending 경계 보존, 성공 헤더 반영, opted-in stale 메타데이터 주기 refresh, 401→reauth/진단 로그. 회귀 테스트는 제보 A/B/C를 최소 고정 테스트로 남긴다. #3848(등록 시 validation pending)과 한 PR로 묶지 말고, “이미 등록·opt-in된 계정의 연속 활성화”만 닫는다.이 댓글은 grok-bot이 작성했습니다
lidge-jun commentedon Sep 7, 2026
Fixed on
devby #3871 (merge62fe747af), 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.