Repository navigation
fix(combo): wait out a short transient lockout on a pinned native Codex turn - #15168
Merged
diegosouzapw merged 2 commits intoOct 2, 2026
Merged
diegosouzapw merged 2 commits into
diegosouzapw merged 2 commits into
Conversation
QuangBlue
force-pushed
the
fix/codex-pin-short-lockout
branch
from
September 30, 2026 04:41
f44a4bc to
078ba78
Compare
…ex turn A native Codex turn pinned to a model was terminated with 400 NATIVE_CODEX_PINNED_MODEL_UNAVAILABLE when the pinned model only carried a short transient model-only lockout set by ANOTHER client's failed request (live: a 5s server_error lockout after an upstream "Internal error during token generation"; the turn arrived 1.4s later). Auto-resume was rightly rejected (unsafe_provider_state), so the turn could not continue. Root cause: isPinnedTargetModelScopedUnusable() treated every model lock as "model gone". The combo cooldown-wait would have absorbed a 5s lock, but the pre-dispatch gate skips locked targets without recording a retry-after, so the attempt loop's wait never engages for them. Fix: a lock that the cooldown-wait predicate accepts (retryable reason, remaining <= maxWaitMs, cooldown-wait enabled) no longer counts as unusable; the pinned path waits it out before dispatch (deducted from the cooldown-wait budget, 499 on client abort). Long locks, non-transient reasons (quota_exhausted, ...) and cooldown-wait disabled keep the old behavior. The termination log now names each pinned target's lock (reason, remaining seconds, failureCount) or "none". Auto-resume alternates are still judged without the waitable-lock allowance: they are dispatched without that wait, so a briefly locked alternate is skipped in favor of a healthy one. Tests: tests/unit/native-codex-turn-pin-short-lockout.test.ts (7 cases; the two short-lock cases fail before the fix, plus client abort and alternate selection). E2E with Codex CLI through a local router and a mock upstream reproduced the 400 and confirmed the turn now completes after the wait.
QuangBlue
force-pushed
the
fix/codex-pin-short-lockout
branch
from
September 30, 2026 07:00
078ba78 to
daa37b2
Compare
diegosouzapw
merged commit Oct 2, 2026
77f2ac7
into
diegosouzapw:release/v3.8.52
47 of 51 checks passed
Owner
|
Thanks @QuangBlue — merged into |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A native Codex turn pinned to a model is terminated with
400 NATIVE_CODEX_PINNED_MODEL_UNAVAILABLEwhen the pinned model only carries a short transient model-only lockout set by another client's failed request.Seen live: one client's
grok-cli/grok-4.6stream failed mid-generation (Internal error during token generation), which set a 5sserver_errormodel lockout on the (single) grok-cli connection. A Codex turn pinned to that model arrived 1.4s later,areAllPinnedTargetsModelScopedUnusable()returned true, auto-resume was rightly rejected (unsafe_provider_state— the body carries continuation state), and the turn was killed. Codex CLI cannot recover from that 400; the user has to start a new turn.Root cause
isPinnedTargetModelScopedUnusable()treats every model lock as "model gone". The combo cooldown-wait would absorb a 5s lock, but the pre-dispatch gate (executeTargetGates.ts, "model locked by resilience") skips locked targets without recording a retry-after, so the attempt loop's cooldown-wait never engages for them.Fix
shouldWaitForComboCooldown: retryable reason such asserver_error/rate_limit, remaining ≤maxWaitMs, cooldown-wait enabled) no longer makes a pinned target unusable.waitForCooldownAwareRetry; 499 on client abort). The wait counts as one cooldown-wait attempt and is charged to the cooldown-wait budget.quota_exhausted, …), cooldown-wait disabled, and every non-native-turn request.reason Ns failureCount=Nornone) so a remaining 400 can be classified from one line.Tests
tests/unit/native-codex-turn-pin-short-lockout.test.ts(7 cases): shortserver_errorlock keeps the turn on the pinned model (fails before the fix), second upstream failure after the wait surfaces a retryable 5xx instead of the terminal 400 (fails before the fix),quota_exhausted/ lock >maxWaitMs/ cooldown-wait disabled keep the 400, client abort during the wait returns 499 without dispatch, auto-resume skips a briefly locked alternate.native-codex-auto-resume,native-codex-auto-resume-guards,native-codex-turn-pin-10379,native-codex-turn-pin-model-scoped-fallback,chatgpt-web-codex-turn-pin.npm run typecheck:core,scripts/check/check-open-sse-typecheck.mjs, eslint (with suppressions),npm run check:cyclesclean.NATIVE_CODEX_PINNED_MODEL_UNAVAILABLE. After: the router logswaiting 1s before dispatch instead of terminating the turnand the turn completes.