Skip to content

[Bug]: Model denial evidence survives same-ID reauthentication without a cached roster #4952

Closed

Description

@lidge-jun

Client or integration

Codex CLI

Area

Authentication and account pool

Summary

An authenticated flagship model refusal is remembered for six hours under account ID and model ID, without credential generation. Reauthenticating the same internal account can leave the previous credential's denial active, avoiding an account that now has access. Expected: evidence from the old credential stops influencing the replacement, and late old replies cannot create or clear replacement evidence.

The release owner explicitly deferred this to the next round's first item. Compared with the earlier repeated-400 behavior, the current routing is an improvement; empty-filter restoration prevents a hard outage. A generation-aware change and its late-response races should be reviewed without release pressure.

Reproduction

Static, constructible trace at a0f611d; not a claimed live reproduction:

  1. With no roster cached, a pool account receives an authenticated unsupported-model refusal for gpt-5.6-sol. tests/codex-integration/codex-model-denial-evidence.test.ts:52 covers recording without roster evidence.
  2. src/codex/observed-model-denials.ts:51-80 stores account/model plus a six-hour expiry, without credential generation.
  3. Reauthenticate the same pool account. src/codex/auth-api/login-flow.ts:192 and :374 reuse the internal ID and write the replacement. src/codex/account-store.ts:160 increments generation; model-entitlements.ts:565 resolves pool:generation:upstream-account identity.
  4. Existing-account reauth keeps pickerVisibilityChanged false (login-flow.ts:350), so catalog convergence returns without refresh (:158).
  5. The only production account-wide forget call is model-entitlements.ts:884, inside a condition requiring an OLD cached roster with changed identity (:877-884). With no prior roster it cannot run.
  6. cachedDeniedCodexAccountIdsForModel unions the unversioned refusal (:1300). A later positive roster removes the account from the returned denied set (:1292) but does not delete the six-hour record. After the five-minute roster expires, the old denial can reappear.

Fix direction: bind evidence to the credential identity captured from the dispatched auth context; validate against current identity after the exclusion-read fence. Make both denial writes and successful-response clears generation-aware. Required race: an in-flight generation-G refusal or success arrives after G+1 is saved and must neither overwrite nor clear G+1 evidence. Keep cache-only routing, no extra upstream fetch, current TTL/entry bound, positive-roster precedence and empty-filter restoration.

Version

2.59.0 dev, a0f611d

Operating system

Cross-platform logic; static source audit on macOS. No local runtime reproduction was run.

Provider and model

OpenAI / ChatGPT Codex account pool; gpt-5.6-sol and other entitlement-preferred flagship models

Logs or error output

No local runtime logs: this release-audit lane forbids local tests, builds, typecheck, installs and proxy execution. Evidence is static source/control-flow tracing, not a claim of executed reproduction.

Screenshots and supporting files

Observed denial store
Conditional invalidation
Same-ID login flow
Denial union and positive grants
Related: #4921, #4906. No credentials or actual account identifiers are included.

Redacted configuration

{"defaultProvider":"openai","providers":{"openai":{"adapter":"openai-responses","baseUrl":"https://chatgpt.com/backend-api/codex","authMode":"forward"}}}

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
    on Sep 17, 2026
  2. added 3 commits that reference this issue on Sep 19, 2026
    863704a
    4b345b3
    09698b9
  3. lidge-jun commented on Sep 19, 2026

    @lidge-jun
    OwnerAuthor

    Closed by #5199, squash-merged into dev as 09698b9b3a.

    Denial evidence is now bound to the credential generation that produced it, so reauthenticating the same account id no longer inherits refusals earned by the credential it replaced. The starting point was #5092, carried with the author credited, and three defects were found in it during review and fixed before landing.

    The first is that main-pool evidence was silently dropped, which quietly reverted #4906 for those accounts — the very regression that issue exists to prevent.

    The second is a stale write fence that only refused a write when newer evidence already existed under the same key. A row with no entry of its own was inserted unconditionally, and at the 512-entry ceiling that eviction fell on a perfectly live neighbour.

    The third is the read fence, which deleted rows it could not prove were superseded. A store read failure is also reported as not-live, so a transient read error could permanently destroy valid evidence. That one is the reason this is worth reading even if you only care about the original symptom: the repair as first written could lose more than the bug it fixed.

    Evidence is exact-head hosted CI at 3252e665bb2c40211702a5f794f38ace99f080df: 29 successful checks and zero failures, including both macOS shards and all four Linux shards. The regressions live in tests/codex-integration/codex-model-denial-evidence.test.ts. No local suite was run in this lane.

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 working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions