Repository navigation
[Bug]: a pool account stuck on failed credential refresh silently drops its models instead of naming the account #4212
Description
Client or integration
Other
Area
Authentication and account pool
Summary
When a pooled Codex account gets stuck on a failed credential refresh, the models it backs disappear from the model list and requests fail, but nothing names the account or the reason. The operator sees "the model is gone / unavailable through the proxy" and has to guess, and the natural conclusion is that OpenCodex broke, not that one account needs re-authentication.
Reported on X by @LjranceLi on 2026-09-10: astra and sol were both unavailable through ocx while turning the proxy off worked (https://x.com/LjranceLi/status/2097930258054025507). They then found the real cause themselves and asked for a clear indication: "It's a problem with one of my accounts: 'Codex credential refresh did not complete.' But could there be a clear indication/prompt for this?" (https://x.com/LjranceLi/status/2097934232643633636).
Expected: when an account becomes unusable, the surfaces that change because of it should say so - the model list should explain why an entitled model vanished, and the account should carry a persistent, visible health reason.
Reproduction
- Register two or more Codex accounts in the pool, at least one of them the only account entitled to an account-gated native model.
- Put that account into a state where the credential refresh keeps failing (expired or revoked refresh grant).
- Start the proxy and open the model list from the client.
- Observe that the gated models backed only by that account are no longer offered, and that the failure text at request time is a generic retry message.
- Observe that no surface tells you which account caused it, or that re-authentication is required.
What the source shows at dev 6d3ad12e3 (2.51.0):
src/codex/account-usability.tsmakes the account unselectable throughisAccountNeedsReauth(accountId), with no reason carried to callers.src/codex/model-entitlements.tsderives gated-model availability from entitled accounts (entitledCodexAccountIdsForModel,availableAccountGatedNativeModels), so losing the last usable entitled account silently removes the model from what is advertised.src/server/responses/codex-auth-error.ts:35returns503 server_busywith "Codex main credential refresh did not complete; retry this request", which reads as a transient server problem rather than an account that needs the operator.src/server/management/oauth-account-routes.ts:280-281already computesneedsReauthandreauthReason: "refresh_failed", so the signal exists on the management side; nothing connects it to the missing model or to the request-time refusal.
Version
2.50.0 as reported ("upgraded to the latest today", 2026-09-10). Source read at dev 6d3ad12e3 (2.51.0), where the same paths are unchanged.
Operating system
Not stated by the reporter; the paths above are platform-independent.
Provider and model
codex / gpt-6-astra and codex-sol (account-gated native models)
Logs or error output
HTTP 503 server_busy
Codex credential refresh did not complete; retry this requestScreenshots and supporting files
X thread: https://x.com/LjranceLi/status/2097930258054025507
Redacted configuration
{}Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
Activity
Ingwannu commentedon Sep 10, 2026
Checked the cited paths at dev 6d3ad12. The account-health/explainability gap is worth tracking, but the quoted 503 is not the generic pooled-account mapping:
nativeMainRefreshFailureResponsehandles native-main refresh errors, returns 401 for a typedreauthfailure, and keeps 503 + Retry-After for retryable/inconclusive refresh.mapCodexAuthContextErrorToResponsealready returns 401 for a non-mainCodexAuthContextError. We should not turn every refresh failure into permanent reauthentication or remove its models irreversibly.Please separate (1) terminal grant failure, (2) transient refresh failure, and (3) no remaining usable entitled account in the diagnostic contract. Operator-facing management diagnostics can correlate a masked account with affected models; ordinary data-plane clients should receive a stable reason/action without disclosure of other pooled accounts. The cited
/api/oauth/accountsprojection also belongs to the generic OAuth store, so its health fields need tracing to the Codex-pool surface rather than assuming they already describe that same account store. Keeping this open; no account/config state changed.lidge-jun commentedon Sep 10, 2026
리뷰 · 우선순위 72 / 80
이 이슈는 Codex 풀 계정 하나의 자격 증명 갱신이 계속 실패할 때, 그 계정이 책임지던 모델이 목록에서 조용히 사라지고 요청도 실패하는데, 어디에도 “어느 계정이 재로그인이 필요하다”는 말이 안 나온다는 버그다. X에서 @LjranceLi가 astra/sol이 ocx를 통하면 안 되고 프록시를 끄면 된다는 증상을 보고한 뒤, 스스로 “Codex credential refresh did not complete”가 원인이라는 걸 찾았고, 그걸 화면/목록에 분명히 보여 달라는 요청으로 이어졌다.
현재
devHEAD6101140ff(package.json 2.51.0, 제품 freeze tip은 아직6d3ad12e3)에서 원인은 이미 코드에 드러나 있다.src/codex/account-usability.ts의isCodexAccountUsable(약 23행)은isAccountNeedsReauth(accountId)가 참이면 풀 계정을 선택 불가(약 50행)로 만들고, 메인 계정도 갱신 허가가 없으면 같은 식으로 막는다. 그런데 이 함수는 “왜 막혔는지” 이유를 호출자에게 돌려주지 않는다.src/codex/routing.ts의getEligiblePoolAccounts(약 1237행)도!isAccountNeedsReauth(account.id)로 같은 계정을 걸러 내지만, 역시 이유를 모델 목록이나 요청 오류로 전달하지 않는다. 계정에 묶인 네이티브 모델은src/codex/model-entitlements.ts쪽에서 쓸 수 있는 계정 집합이 줄어들면 광고 자체가 사라지므로, 운영자 눈에는 “모델이 없어졌다 / 프록시가 고장”으로만 보인다.요청 실패 문구도 오해를 키운다.
src/server/responses/codex-auth-error.ts는 갱신 실패에503server_busy와 “Codex main credential refresh did not complete; retry this request”를 쓴다. 이건 잠깐 바쁜 서버처럼 읽히고, “이 계정은 재인증이 필요하다”로 읽히지 않는다. 한편 공개 OAuth 쪽src/server/management/oauth-account-routes.ts약 284–285행은 이미needsReauth/reauthReason: "refresh_failed"를 내려준다. Codex 풀 계정 DTO는src/codex/auth-api.ts의poolAccountDto(약 367행)에 있고, 여기도needsReauth는 잡지만 모델 목록·요청 오류까지reauthReason을 연결하지는 않는다. 즉 정보는 일부 계정 API에만 있고, 모델 목록과 실패 응답이라는 “사라진 표면”에는 아직 안 붙어 있다.feasibility 메모(L3 #4212)와도 맞다. 이번 라운드는 Codex 계정 표면만 좁혀서,
reauthReason을 모델 목록과 요청 오류에 연결하는 쪽이 맞다. types.ts/config.ts 대규모 쪼개기와는 거의 겹치지 않는 계정·라우팅 표면 수정이다, 닫지 말고 L3 레인에 넣는 것이 좋다.경로/심볼 -
src/codex/account-usability.tsisCodexAccountUsable(~23, ~50):isAccountNeedsReauth로 걸러 내지만 이유를 반환하지 않음경로/심볼 -
src/codex/routing.tsgetEligiblePoolAccounts(~1237): pause/reauth 필터는 있으나 사유를 상위로 전달하지 않음경로/심볼 -
src/codex/model-entitlements.ts: 쓸 수 있는 계정이 없어지면 게이트 모델이 목록에서 조용히 사라짐라인 ~35 -
src/server/responses/codex-auth-error.ts: 503 busy 문구가 “재시도”로 읽혀 재인증 필요와 혼동됨라인 ~284-285 -
src/server/management/oauth-account-routes.ts: 공개 OAuth에는needsReauth/reauthReason이 이미 있음 (풀 DTO는auth-apipoolAccountDto)경로/심볼 -
src/codex/auth-api.tspoolAccountDto(~367):needsReauth는 있으나 모델 목록·요청 오류와 미연결메인테이너의 판단이 필요한 지점
reauthReason값을 공개 OAuth의"refresh_failed"와 풀 DTO에서 통일할지, 풀 전용 문자열을 둘지server_busy를 재인증 전용 코드/문구로 바꿀지, busy는 유지하고 본문만 계정 id를 넣을지너의 추천
L3 레인에 넣고,
poolAccountDto/oauth-account-routes에 이미 있는needsReauth·reauthReason을 모델 목록(엔타이틀먼트 상실 설명)과 요청 오류(codex-auth-error.ts문구)에 연결하라.isCodexAccountUsable만 고치지 말고, 선택 필터의 단일 입구인getEligiblePoolAccounts와 계정 건강 DTO를 같이 쓰라. types/config 대규모 쪼개기를 기다릴 필요는 없다. 이번 라운드에서 계정 표면만 좁혀 구현하라.이 댓글은 grok-bot이 작성했습니다
Merge pull request #4233 from lidge-jun/codex/260911-l3-account-attri…
fix(codex): name the account that left the pool instead of only dropp…
pool: name the account when a refresh fails or its models vanish
lidge-jun commentedon Sep 11, 2026
Landed via #4248 at ac3c3d6
dev에 pool refresh/catalog suppression이 계정을 이름으로 알려 주도록 들어갔습니다. GitHubCloses는 default branch가 아니라dev머지라 이슈가 열린 채로 남아 닫습니다.이 댓글은 grok-bot이 작성했습니다
fix(codex): name the account that left the pool instead of only dropp…
Merge pull request lidge-jun#4233 from lidge-jun/codex/260911-l3-acco…
pool: name the account when a refresh fails or its models vanish