Surface Codex usage limit with reset time and profile switch - #467
Conversation
544b390 to
6db8e4d
Compare
TheGreatAxios
left a comment
There was a problem hiding this comment.
critique · REQUEST CHANGES (posted as comment — reviewer account is the PR author)
The branch parses Codex 429 usage-limit bodies into a reset-time message, a profile-switch hint, and a retry-abort for far-away resets.
Typecheck and full suite pass (4533/0). Retry composition, scope, and conventions are otherwise clean, but the parser's shape assumption contradicts the live backend:
- src/auth/codex/usage-limit-error.ts:47 — VERIFIED: the parser only matches
detail.error.code === "usage_limit_reached", but a live 429 captured today from chatgpt.com/backend-api/codex/responses is top-level{"error":{"type":"usage_limit_reached","message":"The usage limit has been reached","plan_type":"team","resets_at":1786841292,"resets_in_seconds":464758}}— nodetailwrapper, discriminator istype, notcode. On production trafficparseCodexUsageLimitErrorreturns undefined, the message/hint never render,retryAfterMsstays unset, and the retry-abort path never engages — the exact transient-429 retry behavior the PR sets out to fix. - src/auth/codex/usage-limit-error.ts:26 — hand-rolled
typeofguards over external HTTP data; repo convention requires arktype at the boundary (AGENTS.md), with precedent in src/auth/oauth/client.ts. An arktype schema would also have forced the code/type ambiguity into the open. - src/auth/codex/usage-limit-error.ts:66 —
resets_at(absolute epoch, present in the live body) is never read; only the relative-seconds fields are. Fine as a primary choice, but worth a fallback given the shape drift above. - x-codex-* response headers (x-codex-primary-reset-at, x-codex-rate-limit-reached-type) are unread; they are a more stable source than the body if the body shape keeps moving.
Merge-compatibility with the codex-connect-fixes branch was checked: no textual or semantic conflict (the content-type repair decorator only touches ok streaming responses; this PR only reads error bodies).
Fix the shape (accept both type and code, tolerate top-level and detail-wrapped) with an arktype schema and this is good to go.
Parse the live nested detail.error body so 429s classify as quota_exhausted with retryAfterMs from resets_in_seconds. Operator message names plan/profile when known, human reset time, and points at /model for another subscription. Long reset windows already abort via the Corbits retry policy once retryAfterMs is populated.
6db8e4d to
789809b
Compare
Summary
When the Codex ChatGPT backend returns HTTP 429
usage_limit_reached(nesteddetail.errorwithresets_in_secondsandplan_type), Corbits now classifies it asquota_exhausted, surfaces a human reset ETA, names the active Codex profile when known, and points operators at/modelto switch profiles. Long reset windows abort auto-retry instead of hanging.Why
Live quota exhaustion was showing up as a generic inference/retry failure. Operators with multiple Codex profiles (one exhausted, another with quota) had no clear path.
Changes
detail.errorCodex usage-limit bodies (usage-limit-error.ts)quota_exhausted+retryAfterMsin the gateway error path/modelhintproviderIdthrough TUI stream error mapping when presentstore: falseand rejectsprevious_response_id(live-confirmed; no incremental-input path on this backend)Test plan
previous_response_idhard 400 (documented, not implemented)Linear
CL-5888