Repository navigation
[Bug]: native main pool does not refresh expired auth.json tokens #2221
Description
Client or integration
Codex CLI
Area
Authentication and account pool
Summary
When the native Codex __main__ account has an expired access token in ~/.codex/auth.json but still has a valid refresh token, OpenCodex can select that account and forward the expired bearer to /v1/responses. The request returns 401 instead of refreshing the native credential first. Stored account-pool credentials have a refresh path, but the native main credential does not pass through it.
Expected behavior: OpenCodex refreshes the native main credential before upstream I/O. If upstream rejects that bearer once with 401, OpenCodex refreshes and replays the request once with the new bearer.
Reproduction
- Use OpenCodex at commit
caf20353fa87bf14c5fef078e5d8b0692dd7c92don thedevbranch. - Configure the Codex native main account in
~/.codex/auth.jsonwith an expired access token and a non-empty valid refresh token. Ensure no other account-pool credential is currently usable. - Start the local proxy with
ocx start --port 10100. - Send a normal Codex CLI request through
http://127.0.0.1:10100/v1/responses. - Observe that the native
__main__account is selected, but its expired access token is forwarded without first using the refresh token. - Observe the 401 response shown below. The request succeeds only after credentials are refreshed outside this native-main request path.
The same missing native credential substitution affects /v1/responses/compact.
Version
caf20353fa87bf14c5fef078e5d8b0692dd7c92d (dev, before PR #2222)
Operating system
Ubuntu 24.04.4 LTS
Provider and model
OpenAI Codex native account; model-independent
Logs or error output
unexpected status 401 Unauthorized: OpenAI account pool has no usable account credential, url: http://127.0.0.1:10100/v1/responsesScreenshots and supporting files
PR #2222 contains focused regression tests for pre-request refresh, one-time 401 recovery, compact routing, refresh-token rotation, and native/stored credential convergence.
Redacted configuration
{
"native_main": {
"access_token": "<expired-redacted>",
"refresh_token": "<valid-redacted>"
},
"other_usable_pool_accounts": 0
}Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
Activity
github-actions commentedon Aug 20, 2026
Issue reopened
The report now contains the information required by the automated check. Thanks for updating it.
Ingwannu commentedon Aug 21, 2026
Confirmed against current
dev(7881319e7df5cd21276d8fa2fc82d6739e3d23ab): this is still a real native-main authentication gap.src/codex/main-account.tsonly reads and checks the stored access token, andsrc/codex/auth-context.tsmaterializes that token without a nativerefresh_tokenpath. The stored account-pool path has refresh handling, but__main__does not./v1/responses/compactalso has no bounded native-main 401 refresh/replay path.PR #2222 is the linked implementation direction, but it is not merge-ready on its current exact head
f46deab84ba177a0a32354502f486192545fac64. I independently reviewed it and requested changes for the missing compact 401 replay, lack of coordination/CAS with an external Codexauth.jsonwriter, pool-first persistence before the authoritative native file, stale reclaim-lock handling, unsafe test-home isolation, and the unrelatedLEARNED_LESSONS.mdaddition. It is also Draft, conflicting, hygiene-blocked, and requires exact-head maintainer security review before sponsorship.Keeping this issue open as the bug contract. It should close only after a corrected current-
devimplementation lands with pre-request refresh plus exactly one 401 replay for both Responses and compact, fail-closed persistence, external-writer race coverage, and green exact-head CI. The owner/Grok review diagnosis was treated as input and independently verified rather than assumed.lidge-jun commentedon Aug 21, 2026
리뷰 · 우선순위 70 / 80
지금
devHEAD7881319e7에서도 구멍 그대로임.src/codex/main-account.ts:33-51getMainAccountToken()/isMainAccountTokenLive()가~/.codex/auth.json을 읽기만 함. JWTexp지나면 사용 불가. 살아 있어도 리프레시 토큰은 안 봄.src/codex/auth-context.ts:485-506이__main__을main-pool로 고를 때 그 만료 베어러를 그대로 주입함. 풀 계정은:510getValidCodexToken()이src/codex/account-store.ts에서 리프레시함. 메인만 Option A 주석(src/codex/account-usability.ts:35-36) 때문에 빠져 있음.materializeCodexUpstreamAuth(auth-context.ts:595-603)도 같은 읽기 전용 토큰임. 만료 액세스가/v1/responses랑/v1/responses/compact로 가서 401 남. 본문 맞음. Ingwannu가 같은 헤드에서 독립 확인함.재현도 이제 있음. 예전엔 봇이 재현 없어서 닫았음. 다시 열렸고 커밋
caf20353f+ 만료 액세스/유효 리프레시 + 풀 계정 0개 + 401 로그가 박혀 있음. 그 자동 클로즈 다시 하지 말 것. 계약은 이 이슈가 소유자임.짝은 #2222임.
fix(codex): refresh native main account tokens. draft. 이미 grok-bot 리뷰 있음(62). 랜덤 중복으로 닫지 말 것. 다만 그 헤드는 머지 준비 아님. Ingwannu CHANGES_REQUESTED — compact 401 리플레이 없음, 외부 Codexauth.json라이터랑 CAS 없음, 풀 퍼시스트가 네이티브 파일보다 앞섬, stale reclaim-lock, 테스트 홈 격리, 무관한LEARNED_LESSONS.md. hygiene-blocked. 충돌 중. 그 PR을 이 이슈에 맞춰 억지로 열지 말고, 이 이슈가 스펙임. 착지한 구현이 현재dev에서 사전 리프레시 + Responses/compact 401 한 번 리플레이 + fail-closed 퍼시스트 + 외부 라이터 레이스를 잠그면 그때 이 이슈가 닫힘.#2188 L1–L9 사이드카는 이미
dev.x_search넣지 말 것. Grok OAuth Chat 기본(#2255)/GUI 옵트인 Responses(#2266)랑 다른 레인임. 네이티브 Codex 메인 인증임.types.ts/config.ts스플릿은 진행 중인데 이 이슈는 스키마 PR이 아님. 프리뷰 배포 아님. v2.28.0 이미 태그됨. 핫 크래시는 아닌데 메인만 있는 오퍼레이터는 프록시가 401로 죽음. 풀은 리프레시하고 메인만 안 함. 그래서 70.src/codex/account-store.ts:490-523풀 리프레시가 이미 grant 핑거프린트랑 락이랑 원자 기록을 갖고 있음. 메인 경로를 그 계약에 수렴시키면 됨. 새 토큰 스토어를 만들지 말 것.auth.json을 쓰기 시작하면 Codex CLI랑 파일 레이스가 생김. 락이 그거 막아야 함. 리프레시만 있고 액세스가 빈 파일도 후보로 넣어야 함. 지금getMainAccountToken()은 액세스 없으면 null임.해결방안: 이 이슈는 계약으로 열어 둬라. 구현은 현재
dev에 리베이스한 #2222 후속(또는 동급). 요청 전에 네이티브 메인 리프레시. Responses랑 compact 둘 다 401이면 한 번만 리플레이. 이미 바디가 나온 뒤엔 리플레이 금지.auth.json쓰기 실패는 fail-closed. 외부 라이터 CAS.LEARNED_LESSONS.md넣지 말 것. 스플릿이main-account.ts/auth-context.ts를 다시 옮기면 리베이스하지 말고 닫고 다시 짜라. 지금은 그 파일 구조 그대로라 그 정도 아님.이 댓글은 grok-bot이 작성했습니다
devlog: backlog disposition program roadmap (work-phase 0, docs-only)
lidge-jun commentedon Aug 25, 2026
Investigated. Not fixed yet, and I want to be precise about why, because the safe-looking half is not actually separable.
First, a correction to the reported symptom on current
dev: an expired native-main bearer is not forwarded upstream.isMainAccountTokenLive()decodes the JWTexpand rejects an expired token (src/codex/main-account.ts:44-50), so the request fails locally rather than producing a 401 from the upstream. I verified that with a scratch regression that made zero network calls.So the remaining defect is narrower than the title but still real: native main never refreshes, while pool accounts do through a locked, generation-guarded path (
src/codex/account-store.ts:486-530).The obvious minimal fix — refresh before upstream I/O, skip the 401-replay machinery — is what I set out to ship, and it does not work in isolation:
src/oauth/chatgpt.ts:146-160)atomicWriteFilereplaces the target unconditionally (src/config/atomic-write.ts:95-115); there is no compare-and-swap against an external writerauth.jsonSo refreshing without a crash-safe, external-writer-aware publication protocol can strand the account: we obtain a rotated grant, another writer clobbers it or we crash mid-publish, and the old refresh token is already spent. That is a worse failure than the one being fixed — the current behavior fails a request; the naive fix can lose the login.
#2497 proposes a publisher for exactly this, and it is held under security review rather than merged. Three blockers were verified by hand: the rename-before-link window leaves no
auth.jsonon a crash with no startup recovery; a same-account fallback adopts a different pool refresh grant into native-main (account-id equality is not grant ownership — the hazardanthropic-routing.tsfails closed on); and the "exactly one" 401 replay expands to up to nine physical sends throughfetchWithTransientRetry. Reusing its publisher would import the first blocker.What this needs is a designed and reviewed publication protocol for
auth.json, not a smaller patch. The 401 replay and the cross-grant adoption can stay excluded from that scope. Leaving this open rather than shipping something that looks like a fix and can cost someone their credential.Merge from upstream (#8)
MarcTCruz commentedon Aug 28, 2026
Status update: PR #2497 is not mergeable in its current form. Current
devis80e251a4db5ab600ef0a0b44c9d24b326c37d40b; PR head86a49e852581835253a06e40ebb7012215820757is 419 commits behind and GitHub reports merge conflicts. The attempted rebase has semantic conflicts on the credential and entitlement path, so resolving it would require reauthoring authentication behavior and a new security review.Keep this issue open as the bug contract. PR #2497 remains Draft and unsponsored; it must not close this issue. A successor should be based on current
dev, narrowly scoped, and reviewed for safe publication of~/.codex/auth.jsonbefore the issue is closed.fix(codex): refresh expired native main auth.json tokens (#2221) (#2848)
lidge-jun commentedon Aug 28, 2026
Fixed on
devby commit 182b356 (PR #2848), reimplementing @MarcTCruz's #2497 on the current tree.The native main pool now refreshes an expired
auth.jsontoken before the request instead of failing it. Previouslysrc/codex/main-account.tsonly read access tokens — there was no refresh or publish path at all.The write path got most of the review attention, since it touches the credential file the Codex CLI also owns:
O_CREAT|O_EXCLand its0600mode is verified before any secret byte is written (Windows ACLs are applied pre-write too). The first implementation wrote the token first and hardened afterwards, so a pre-existing 0644 temp file stayed world-readable during the write.auth.jsonis never truncated in place, and a refresh failure leaves it byte-identical.auth.json— that credential is not ours to refresh.Verified with
tests/codex-main-account-refresh.test.tsandtests/responses-native-main-refresh.test.tsdriven RED then GREEN, plus the auth-context, server-auth, and replay-identity suites,tsc, andprivacy:scan.This PR targeted
dev, so GitHub did not auto-close the issue — closing manually.fix(codex): refresh expired native main auth.json tokens (lidge-jun#2221
devlog: backlog disposition program roadmap (work-phase 0, docs-only)
devlog: WP5 security audit — native main refresh needs a decision bef…
fix(codex): refresh expired native main auth.json tokens (lidge-jun#2221