Repository navigation
fix(server): Codex stops keep their reset time when the last usage update reads 99% - #21186
jaikhuranna wants to merge 2 commits into
Conversation
…date reads 99% Codex reports whole percentages, so a turn can be refused while the window that ran out still reads 99%. The reset helper only accepted windows at 100%, so those stops lost a reset the server already had and clients offered manual retry instead of auto-resume. When no window reads 100% and exactly one reads 99%, use that window's reset. If several read 99% it is unclear which ran out, so the stop waits for an update at 100% as before. Windows whose reset has passed no longer count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — This is a focused Codex usage-limit bug fix that preserves existing recovery behavior while filling in reset times only for an unambiguous active 99% window. It introduces no schema, deployment, default, security, or static-analysis changes, and the behavior is covered by targeted tests. You can add or adjust custom eligibility rules. Learn more. |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks @jaikhuranna for the reproduction and focused fix. Closing this as a duplicate of #18721 by @voltcrash, now maintained by @juliusmarminge; its newer branch addresses the same 99% reset and the missing-snapshot case. The two approaches differ when both windows read 99%, so your conservative ambiguity handling is useful context for that PR. |
Thanks for the credit @juliusmarminge . I was thinking of another approach of getting reset times. Maybe we can get them from the providers themselves instead of the "usage limit reached" response. we can consistently get when it's resetting. just like how we show it in the usage limits tab. This would be more consistent than relying on their response which may change. What do you think?
Update: i'm checking if someone has already worked on it. I think this will make it consistent for all Providers instead of just codex in this case. |

Problem
When Codex refuses a turn for a usage limit, the stop sometimes has no reset time. Clients then say "the provider did not report a reset time" and offer only a manual retry, even though Codex had already sent the reset.
Codex reports usage in whole percentages, so the last
account/rateLimits/updatedbefore a refusal can still read 99%.codexUsageLimitResetAtonly counted windows at 100%. In one log, two threads hit the limit 2.4 s apart with the sameresetsAt:codexupdate before the refusalfailure.resetAtprimary: { usedPercent: 99, resetsAt: 1792287729 }, secondary 16%nullprimary: { usedPercent: 100, resetsAt: 1792287729 }2026-10-18T01:42:09.000ZChange
All changes are in
codexUsageLimitResetAt. The adapter only passes it the current time.Scope and approval
Fixes the first case in #14169, which a maintainer triaged here.
This overlaps #18721 ("fix(server): Codex usage-limit stops keep their reset time"). That PR also adds a fallback to the published limits for sessions refused before any rate-limit update. Its branch currently conflicts with
main. This PR is the narrower 99% fix on currentmain; if 18721 lands first, close this one. I posted an end-to-end reproduction of the 99% case on 18721 here.Verification
Real client. An isolated dev server with showcase data, its Codex binary pointed at a wrapper around the real
codex app-server. The wrapper answersturn/startwith a 99% primary update (secondary 16%) and then ausageLimitExceededfailure: thread A's sequence. It drops the real account's rate-limit updates.main)The banner's reset (5:24:45 PM) matches the faked window's
resetsAtto the second. The after shot was taken on an earlier revision of this commit, which takes the same path for this case. On the final commit that path is covered by therounded-resetreplay below.Tests.
vp test run apps/server/src/provider/codexUsageLimits.test.ts apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts: 195 passed.rounded-reset: 99% update, then refusal; the stop gets the reset.late-rounded-reset: refusal, then a 99% update; the stop gets the reset.ambiguous-late-reset: both windows at 99%, so the stop has no reset; a 100% update then fills it.tsc-rs --noEmitinapps/serverpasses.vp linton the touched files reports only two existing warnings on lines this PR doesn't touch.Not covered: a refusal where one window reads 99% but a different window actually ran out after jumping 2% or more between updates. The rule would pick the 99% window's reset, which is the later one when that is the weekly window. This needs both windows near the limit at once, so it is rare.
Merge danger
Door: two-way. No stored data or contracts change, and reverting restores today's behavior.
Blast radius: small. It affects only Codex usage-limit stops: more of them get a reset time, so clients offer resume or snooze at reset instead of manual retry.
Model and harness: Claude Opus 5.5 (
claude-opus-5-5) in Claude Code, run from T3 Code. Reviewed over four rounds by GPT-6.1 Sol (Codex) and Claude Opus 5.5.🤖 Generated with Claude Code