TUI blocks all models when primary bucket hits 100%, even models that are not actually rate-limited
Summary
When a long-running codex TUI session sees rate_limits.primary.used_percent reach 100% (regardless of which model triggered it), the session refuses to send further requests on any model — including models the server is not actually blocking, like gpt-5.3-codex-spark on a prolite plan.
A fresh codex session started in a new terminal works fine on the same model that the affected terminal refuses to use. The web portal correctly shows separate usage stats for spark vs. the main models, but the CLI's rate-limit envelope conflates them into a single limit_id: "codex" bucket.
Reproduction
- Start codex on
gpt-5.5(or whatever the default primary model is on aproliteplan) - Burn through the 5-hour primary bucket until rate-limits return 100% on the primary window
- In a separate codex terminal already running
gpt-5.3-codex-spark, attempt to send a message - The spark terminal refuses to send, even though server-side spark is not blocked
- Open a brand-new terminal, launch
codex, switch togpt-5.3-codex-spark→ works immediately on the same account
Expected Behavior
The TUI should use rate_limit_reached_type (the explicit server signal) to decide whether to block a request, not primary.used_percent alone. When rate_limit_reached_type: null, the server is permitting the request and the client should send it.
Alternatively, the rate-limit envelope should be enriched server-side to distinguish per-model availability the way the web portal does.
Evidence
I inspected a past spark session from a prolite account where rate_limits.primary.used_percent was 100% the entire time:
{
"limit_id": "codex",
"primary": {
"used_percent": 100.0,
"window_minutes": 300,
"resets_at": ...
},
"secondary": { "used_percent": 62.0, "window_minutes": 10080, ... },
"credits": { "has_credits": false, "balance": "0" },
"plan_type": "prolite",
"rate_limit_reached_type": null
}
That session logged 2275 successful response events at primary=100%. The server allows spark at 100% primary — the field rate_limit_reached_type: null confirms it.
Impact
Users hitting the primary bucket lose access to spark in any pre-existing terminal even though their spark allowance is unused. They must quit and restart codex to recover access. For long-running terminal sessions (tmux panes, persistent agents), this is a forced session loss with no workaround inside the running TUI.
Environment
codex-cli 0.130.0- Plan:
prolite - Default model:
gpt-5.5 - Affected model when this happens:
gpt-5.3-codex-spark
Suggested Fix
In the TUI's request gate, change the decision from:
if rate_limits.primary.used_percent >= 100: block_request()
to:
if rate_limits.rate_limit_reached_type is not None: block_request()
The used_percent: 100 value is informational (drives the UI meter), not authoritative for whether a specific request will be allowed. The authoritative signal is rate_limit_reached_type.
3 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Why this is not a duplicate of #23150
These bugs are related but operate at different layers, and only one of them explains what users actually observe in the TUI:
#23150 is server-side accounting: spark calls being recorded against the
codexbucket meter in addition to thecodex_bengalfoxbucket. It's a telemetry/visibility issue — the numbers shown to the user are misleading, but the server's actual block decisions are independent of those numbers.#23171 (this issue) is client-side decision logic: the TUI refuses to send requests for any model when
primary.used_percenthits 100, even when the server explicitly signals no block is in effect (rate_limit_reached_type: null). The block decision happens before any network call to the server.The empirical proof these are independent:
gpt-5.3-codex-sparksucceeds immediately while an existing terminal on the same model on the same account refuses. If #23150's cross-bucket draining caused real server-side blocking of spark, the new terminal would fail too. It doesn't.019e2d84-604c-7493-897c-ab5d7cd1dd05(started2026-05-15T21:21:50) logged 2275 successful response events withprimary.used_percent: 100.0andrate_limit_reached_type: nullthroughout. The server actively let spark through at 100% primary.proliteaccount, the portal shows "38% remaining weekly" and the CLI'ssecondary.used_percentreads exactly62.0. The buckets that the CLI sees are the same buckets the user sees in the portal.019e3346-899c-7be2-8d37-ebdb916a26a4) had last received a rate-limit envelope 11 hours earlier (primary: 100%, secondary: 31%, rate_limit_reached_type: null). I quit the TUI, resumed the session, and sent a new message — the TUI returned an "out of tokens" error. The session log shows:task_startedevent at2026-05-17T17:11:36.690Zrate_limitsenvelopeThe TUI gated on the 11-hour-old stored
primary.used_percent: 100from the session file and refused to send the request to the server. No API call was made, no fresh envelope received. The actual account state at that moment wassecondary: 62%(per a live concurrent session019e36e1-0778-7091-b692-3e834df3fd21and per the web portal) — but the resumed session was making its decision fromsecondary: 31%stored state, which proves the gating logic is operating on cached/persisted local data, not on a fresh server check.Fixing #23150 (correcting cross-bucket accounting) would clean up the displayed numbers but would NOT fix this bug. As long as the TUI gates on
used_percent >= 100from cached/persisted state instead ofrate_limit_reached_typefrom a fresh server poll, existing sessions and resumed sessions will continue to lock out users from models the server is happy to serve.Note on CLI version: my evidence is from
codex-cli 0.130.0, which surfaces onlylimit_id: "codex"andlimit_id: "premium". Thecodex_bengalfoxbucket #23150 references appears starting in0.131.0-alpha.9. The bug I'm describing predates that version.Happy to provide the relevant rollout files privately if it would help debugging.
Honest correction to the diagnosis above
After more inspection, I have to walk back some of my claims about exactly where the gate fires.
I had hypothesized that the TUI gates on the rollout file's most recent
event_msg/token_countevent. But inspecting a different locked session (29MB, 31 user messages, 1631 assistant turns) shows the lasttoken_countwas alreadyprimary.used_percent: 0.0, rate_limit_reached_type: null— yet the session was still exhibiting the lock pattern (sub-secondtask_completeaftertask_started, noagent_message, no error event written). The same session also has a successful 6-second task interleaved between two failed sub-second ones, with no newtoken_countevents between them.That rules out "TUI gates on the rollout's last token_count" as a complete explanation. The gate is more likely driven by:
What I can still defend:
task_completewith no model output, no rate-limit envelope, no error response in the session log)rate_limit_reached_typeisnullwhen these locks fire (confirmed by adjacent live sessions polling the same account)What I cannot defend without further testing:
The core ask in this issue still stands: the TUI is gating against models the server is willing to serve, and the gate's authoritative signal should be
rate_limit_reached_type, notused_percent. I'll update with a cleaner reproduction the next time the bucket fills.