Skip to content

[Bug]: a pool account stuck on failed credential refresh silently drops its models instead of naming the account #4212

Closed

Description

@lidge-jun

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

  1. Register two or more Codex accounts in the pool, at least one of them the only account entitled to an account-gated native model.
  2. Put that account into a state where the credential refresh keeps failing (expired or revoked refresh grant).
  3. Start the proxy and open the model list from the client.
  4. 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.
  5. 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.ts makes the account unselectable through isAccountNeedsReauth(accountId), with no reason carried to callers.
  • src/codex/model-entitlements.ts derives 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:35 returns 503 server_busy with "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-281 already computes needsReauth and reauthReason: "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 request

Screenshots 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

  1. added
    bugSomething isn't working
    account-poolOAuth, credentials, Codex pool, quota, failover, plans
    catalogModel catalog, slugs, visibility, routed entries
    on Sep 10, 2026
  2. Ingwannu commented on Sep 10, 2026

    @Ingwannu
    Owner

    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: nativeMainRefreshFailureResponse handles native-main refresh errors, returns 401 for a typed reauth failure, and keeps 503 + Retry-After for retryable/inconclusive refresh. mapCodexAuthContextErrorToResponse already returns 401 for a non-main CodexAuthContextError. 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/accounts projection 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.

  3. lidge-jun commented on Sep 10, 2026

    @lidge-jun
    OwnerAuthor

    리뷰 · 우선순위 72 / 80

    이 이슈는 Codex 풀 계정 하나의 자격 증명 갱신이 계속 실패할 때, 그 계정이 책임지던 모델이 목록에서 조용히 사라지고 요청도 실패하는데, 어디에도 “어느 계정이 재로그인이 필요하다”는 말이 안 나온다는 버그다. X에서 @LjranceLi가 astra/sol이 ocx를 통하면 안 되고 프록시를 끄면 된다는 증상을 보고한 뒤, 스스로 “Codex credential refresh did not complete”가 원인이라는 걸 찾았고, 그걸 화면/목록에 분명히 보여 달라는 요청으로 이어졌다.

    현재 dev HEAD 6101140ff (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는 갱신 실패에 503 server_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.ts isCodexAccountUsable (~23, ~50): isAccountNeedsReauth로 걸러 내지만 이유를 반환하지 않음
    경로/심볼 - src/codex/routing.ts getEligiblePoolAccounts (~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-api poolAccountDto)
    경로/심볼 - src/codex/auth-api.ts poolAccountDto (~367): needsReauth는 있으나 모델 목록·요청 오류와 미연결

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

    • L3 레인 범위: 계정 카드/CLI 상태만이 아니라 모델 목록 설명과 요청 오류 문구까지 한 번에 묶을지, 단계로 나눌지
    • reauthReason 값을 공개 OAuth의 "refresh_failed"와 풀 DTO에서 통일할지, 풀 전용 문자열을 둘지
    • 게이트 모델이 “사라지는” 대신 “계정 X 재인증 필요”로 남는 빈 슬롯/배너를 둘지, 아니면 목록에서 빼고 별도 경고만 둘지
    • 503 server_busy를 재인증 전용 코드/문구로 바꿀지, busy는 유지하고 본문만 계정 id를 넣을지

    너의 추천
    L3 레인에 넣고, poolAccountDto/oauth-account-routes에 이미 있는 needsReauth·reauthReason을 모델 목록(엔타이틀먼트 상실 설명)과 요청 오류(codex-auth-error.ts 문구)에 연결하라. isCodexAccountUsable만 고치지 말고, 선택 필터의 단일 입구인 getEligiblePoolAccounts와 계정 건강 DTO를 같이 쓰라. types/config 대규모 쪼개기를 기다릴 필요는 없다. 이번 라운드에서 계정 표면만 좁혀 구현하라.

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

  4. added a commit that references this issue on Sep 11, 2026
  5. lidge-jun commented on Sep 11, 2026

    @lidge-jun
    OwnerAuthor

    Landed via #4248 at ac3c3d6

    dev에 pool refresh/catalog suppression이 계정을 이름으로 알려 주도록 들어갔습니다. GitHub Closes는 default branch가 아니라 dev 머지라 이슈가 열린 채로 남아 닫습니다.

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

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 workingcatalogModel catalog, slugs, visibility, routed entrieslanded-via-maintainerOriginal PR closed after landing via a maintainer merge train

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions