Repository navigation
[Bug]: Model denial evidence survives same-ID reauthentication without a cached roster #4952
Description
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:
- 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.
- src/codex/observed-model-denials.ts:51-80 stores account/model plus a six-hour expiry, without credential generation.
- 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.
- Existing-account reauth keeps pickerVisibilityChanged false (login-flow.ts:350), so catalog convergence returns without refresh (:158).
- 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.
- 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
fix(codex): scope model denial evidence to the credential generation
fix(codex): scope model denial evidence to the credential generation
fix: stop one account failure from taking the whole proxy out of serv…
lidge-jun commentedon Sep 19, 2026
Closed by #5199, squash-merged into
devas09698b9b3a.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-poolevidence 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 intests/codex-integration/codex-model-denial-evidence.test.ts. No local suite was run in this lane.