Skip to content

[Bug]: v2.57.0 still selects Free account for Sol/Astra after roster refresh #4906

Closed

Description

@snowiscold1

Client or integration

OpenCodex Codex account pool on Windows

Area

Codex account entitlement-aware routing

Summary

This remains reproducible after the fix landed in PR #4797 / OpenCodex v2.57.0.

The pool contains multiple Codex OAuth accounts: one main Plus account, three Free accounts, and one additional Plus account. After running the account quota refresh and model catalog sync, OpenCodex still selected a Free account for both gpt-5.6-sol and gpt-6-astra, then returned the upstream unsupported-model 400 instead of selecting an entitled Plus account or retrying on one.

The previous issue was #4768. This report is a follow-up verification for the behavior after #4797 landed.

Expected behavior

When a requested model has account-specific availability, the pool should not select an account whose authenticated roster confirms that it cannot serve the model. If no confirmed denial is available, the implementation should still provide a bounded alternate-account retry for the exact pre-stream unsupported-model 400 when another account exists.

Reproduction

  1. Install OpenCodex v2.57.0 on Windows 11 24H2.
  2. Configure a pool containing Plus and Free Codex OAuth accounts.
  3. Run opencodex account refresh openai.
  4. Run opencodex sync --restart-codex.
  5. Start a new request using gpt-5.6-sol.
  6. Repeat using gpt-6-astra.
  7. Inspect opencodex logs --jsonl.

Version

OpenCodex 2.57.0 (@bitkyc08/opencodex@2.57.0).

Operating system

Windows 11 24H2.

Provider and model

Codex / ChatGPT OAuth account pool.

Models affected:

  • gpt-5.6-sol
  • gpt-6-astra

Observed result

For gpt-5.6-sol:

status: 400
provider: openai-pd56bf5
accountLogLabel: pd56bf5
upstreamError: The 'gpt-5.6-sol' model is not supported when using Codex with a ChatGPT account.
attempts: 1
recoveryKinds: []
moveReasons: ["healthy"]

For gpt-6-astra:

status: 400
provider: openai-pd56bf5
accountLogLabel: pd56bf5
upstreamError: The 'gpt-6-astra' model is not supported when using Codex with a ChatGPT account.
attempts: 1
recoveryKinds: []
moveReasons: ["healthy"]

The selected account label corresponds to the Free account shown by opencodex account list openai as selected.

Additional context

The account quota refresh completed successfully and returned quota data for all five accounts. The model catalog sync also completed successfully and appended 83 models. However, the refresh did not produce usable per-account model-roster denial evidence for Sol/Astra, or the denial evidence is not being consumed by the initial native Codex selection path.

The v2.57.0 source contains ENTITLEMENT_PREFERRED_NATIVE_OPENAI_MODELS and cachedDeniedCodexAccountIdsForModel, but the live behavior above still routes to the Free account and performs no alternate attempt.

A local WebSocket warning also appeared:

failed to connect to websocket: HTTP error: 426 Upgrade Required, url: ws://127.0.0.1:10100/v1/responses

The relevant terminal failure remains the account-specific unsupported-model 400.

Requested fix

Please investigate and fix the remaining gap so that:

  1. Full authenticated /codex/models roster refresh is available for every pool account, not only quota refresh.
  2. Confirmed per-account model denial is persisted and consumed by the initial native Codex account selector for Sol/Terra/Luna/Astra.
  3. The exact unsupported-model 400 reliably triggers a bounded alternate-account retry when the request is replayable and another account is available.
  4. Add an integration/regression test covering a Plus + Free pool for gpt-5.6-sol and gpt-6-astra.
  5. Make the logs expose a redacted routing reason such as account-model-denied or entitlement-unknown.

Please relate this to the original issue and verify against a current Windows installation.

Checks

  • I searched existing issues and related PRs.
  • I removed account emails, tokens, API keys, and private credentials.

Related: #4768
Fix under test: #4797

Activity

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

    @github-actions
    Contributor

    Issue reopened

    The report now contains the information required by the automated check. Thanks for updating it.

  3. lidge-jun commented on Sep 17, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 62 / 80

    이 이슈는 Windows에서 Codex OAuth 풀(Plus+Free)을 쓴 뒤에도 gpt-5.6-sol / gpt-6-astra 요청이 Free 계정(openai-pd56bf5)으로 가서 upstream unsupported-model 400을 맞고, attempts: 1·recoveryKinds: []·moveReasons: ["healthy"]만 남는다는 후속 보고다. 원본은 #4768이고, #4797이 2.57.0에 들어간 뒤에도 같은 증상이 남는다는 검증이다.

    지금 dev HEAD 41f1832f6(패키지 2.58.0)에는 #4797(f35fbe8ef)가 이미 있다. src/codex/routing/selection.ts의 withoutModelDeniedAccounts / preferModelEntitledAccount 주석이 바로 이 모양을 설명한다. ACTIVE Free 커서가 eligible 목록을 안 거치면 Sol/Astra가 계속 Free로 가고 400만 반복한다는 것, 그래서 roster denial이 있으면 같은 요청 안에서 entitled 계정으로 한 번 고친다는 것이다. 또한 tip에는 2.57.0 트리에 없던 #4883(b60a6283c, pool eligibility를 caller-owned preview read fence 안에서 닫기)도 있다. 보고 버전은 명시적으로 2.57.0이므로, tip/2.58.0과의 간격부터 갈라야 한다.

    로그가 가리키는 핵심은 「roster가 Free를 이미 denial했는데도 선택됐다」인지, 「denial 증거가 비어 있어서 ordering이 no-op이고 alternate-account retry도 안 탔다」인지다. preferModelEntitledAccount는 deniedModelAccountIds가 없거나 active가 denial 집합에 없으면 항등함수다. 핀된 active도 그대로 둔다. 그리고 주석대로 사전 거절이 아니라 ordering + 400 후 bounded retry가 안전망이다. 그런데 이 로그는 attempts: 1이라 retry 안전망도 안 탄 것처럼 보인다. 그래서 tip에서도 구멍일 수 있고, 2.57.0에서 roster/refresh 타이밍·fence 문제일 수도 있다.

    다음 확인이 없으면 코드 패치를 바로 단정하기 어렵다. (1) 같은 풀로 2.58.0/dev에서 재현되는지. (2) 그때 deniedModelAccountIds/roster에 Free가 sol·astra denial로 잡히는지. (3) Free가 manual pin인지. (4) unsupported-model 400 분류가 alternate-account retry 조건과 문자열/코드가 일치하는지. types.ts/config.ts 분할·preview deploy와는 무관하다.

    우선순위는 중상이다. 계정 풀+게이티드 모델은 체감이 크고, #4797 후에도 남는다면 tip 회귀/잔여 구멍 가능성이 있다. 다만 보고 트리가 2.57.0이므로 먼저 tip에서 재현을 가르는 게 맞다.

    로그 attempts:1 / recoveryKinds:[] - alternate-account retry 안전망이 안 탄 신호. tip에서 분류·트리거를 대조해야 한다.
    src/codex/routing/selection.ts preferModelEntitledAccount - denial 증거가 있을 때만 Free 커서를 보정. 증거 없으면 항등.
    #4797 on 2.57.0 / #4883 on tip only - 보고 버전과 tip 사이에 fence 관련 추가 수정이 있다.
    account refresh + sync 후 재현 - roster가 denial을 채웠는지 로그/덤프가 더 필요하다.

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

    • 보고자에게 2.58.0/dev 재현을 먼저 요청할지, tip 기준으로 바로 조사 이슈로 승격할지
    • attempts:1이 retry 미발동인지, 400 분류 실패인지
    • Free 계정 pin 여부·roster denial 스냅샷을 필수 재현 자료로 받을지

    너의 추천
    먼저 같은 풀로 2.58.0/dev 재현을 요청. tip에서도 attempts:1이면 deniedModelAccountIds 전달 경로와 unsupported-model retry 분류를 추적하는 수정 PR 후보. tip에서 사라지면 2.57.0 한정으로 안내 후 클로즈.

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

  4. lidge-jun commented on Sep 17, 2026

    @lidge-jun
    Owner

    Fixed on dev by #4921, squashed as 987456350b57b5b060d445015598659f9659ab87.

    The ordering rules were already correct; they had no evidence to act on. withoutModelDeniedAccounts and preferModelEntitledAccount both read cachedDeniedCodexAccountIdsForModel, which is cache-only, and the roster cache expires five minutes after a catalog sync fills it. Nothing on the flagship request path refilled it, so the denial set was undefined for most requests, both rules degraded to the identity function, and the pool selected on quota alone — which is the Free account.

    The refusal itself was the missing evidence. A 400 whose body is exactly The '<model>' model is not supported when using Codex with a ChatGPT account. is authenticated, account-specific and model-specific, and it was being spent on one retry and discarded. src/codex/observed-model-denials.ts now records it per (account, model) with six-hour retention, cachedDeniedCodexAccountIdsForModel unions it with the roster-derived denials, and the pair is cleared when that account successfully serves that model.

    One detail worth recording because it silently disabled two existing recovery paths: detection used to rebuild the sentence from route.modelId, but applyCodexAccountGatedWireNormalization rewrites Daybreak to gpt-5.6-sol before dispatch, so upstream named Sol while the route still said Daybreak. The comparison never matched, which turned off both the alternate-account retry and the eight-rung same-account ladder for exactly that model. It now reads the model upstream actually named.

    This stays evidence for ordering rather than a gate: a denied account is still restored when filtering would empty the candidate list, the active account is still returned unchanged when no entitled alternative exists, and an operator's manual pin is still exempt.

    Shipping in the next release off dev.

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 entries

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions