Update 2026-07-23 — I have located the gate in the shipped binary, captured
the suppression live, and confirmed it with a pre-registered prediction (the
derived status cleared itself at the predicted time and suggestions resumed with
no user action — see "Prediction, then observation" below). Replacing my original
reproduction steps, which described only one of the two ways to reach the warned
state.
Root cause
From the 2.1.218 bundle (byte-identical in 2.1.216 and 2.1.217):
function Vxy(e){
if(!e.promptSuggestionEnabled) return "disabled";
if(e.pendingWorkerRequest||e.pendingSandboxRequest) return "pending_permission";
if(e.elicitation.queue.length>0) return "elicitation_active";
if(e.toolPermissionContext.mode==="plan") return "plan_mode";
if(mie().status!=="allowed") return "rate_limit";
return null
}
mie().status is one of allowed | allowed_warning | rejected. The strict
inequality means the still-fully-usable allowed_warning state is treated
exactly like rejected.
Its only caller is the suggestion generator, which bails and emits telemetry:
let l=r(), c=Vxy(l);
if(c) return s7(c,void 0,o), null; // tengu_prompt_suggestion {outcome:"suppressed", reason:c}
Both entry points reach it, so this affects the TUI ghost text and the SDK
prompt_suggestion message identically:
Mnd(e,t) — interactive, querySource.startsWith("repl_main_thread"), source "cli"
- the print/SDK path — source
"sdk"
Two routes into allowed_warning (this is what my original report missed)
Txu(headers) re-derives the status rather than passing the header through.
When the header is allowed or allowed_warning, it calls zzg(); if that
returns a warning object the derived status is allowed_warning, otherwise it is
normalized down to allowed.
zzg warns on either a -surpassed-threshold header, or this pace table:
Uzg=[
{rateLimitType:"five_hour", claimAbbrev:"5h", windowSeconds:18000,
thresholds:[{utilization:0.9, timePct:0.72}]},
{rateLimitType:"seven_day", claimAbbrev:"7d", windowSeconds:604800,
thresholds:[{utilization:0.75,timePct:0.6},
{utilization:0.5, timePct:0.35},
{utilization:0.25,timePct:0.15}]}
]
with Gzg(resetsAt, windowSeconds) returning the fraction of the window
elapsed, and the rule being
utilization >= f.utilization && elapsedPct <= f.timePct.
That is an ahead-of-pace test, not a near-the-limit test. On the weekly claim
it fires at 25% used in the first 15% of the week, 50% in the first 35%, or 75%
in the first 60%. A user who front-loads their week is suppressed for days while
every request still succeeds.
Live capture — 2026-07-23 08:04 EDT, v2.1.218, Linux
Server response headers on /v1/messages:
anthropic-ratelimit-unified-status = allowed
anthropic-ratelimit-unified-7d-status = allowed
anthropic-ratelimit-unified-7d-utilization = 0.62
anthropic-ratelimit-unified-7d-reset = 1785218400
anthropic-ratelimit-unified-5h-utilization = 0.53
anthropic-ratelimit-unified-representative-claim = five_hour
Client-derived state, from the stream-json rate_limit_event:
{"status":"allowed_warning","resetsAt":1785218400,
"rateLimitType":"seven_day","utilization":0.62,"isUsingOverage":false}
The server said allowed. The client stamped itself allowed_warning because
0.62 >= 0.5 and only 0.3219 of the week had elapsed (window opened
2026-07-21 02:00 EDT).
Client telemetry from the same run (base64 additional_metadata decoded):
tengu_prompt_suggestion_init -> {"subscription_type":"max","enabled":true,"source":"env"}
tengu_prompt_suggestion -> {"subscription_type":"max","source":"sdk",
"outcome":"suppressed","reason":"rate_limit",
"prompt_id":"user_intent"}
Enablement passed. The runtime gate suppressed with reason: "rate_limit".
No prompt_suggestion message was emitted on any probe turn.
For completeness, my earlier report was based on a June capture in which the
header itself read allowed_warning — so both routes are real and both land
on the same gate.
Reproduction — headless, no TUI, no waiting for a usage window
CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=1 \
claude -p "say the word apple, nothing else" \
--output-format stream-json --verbose --prompt-suggestions
CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=1 \
claude -p --continue "now say the word banana, nothing else" \
--output-format stream-json --verbose --prompt-suggestions
Two turns are required — the generator early-returns early_conversation below
two assistant messages. Then read the second run's output:
rate_limit_event.rate_limit_info.status is the derived status
- a
prompt_suggestion message present = fired; absent while the derived status
is allowed_warning = suppressed by this gate
Prediction, then observation (2026-07-23)
Ahead of time I predicted: with the 7d claim ahead of pace, the rule
{utilization:0.5, timePct:0.35} stops matching the moment window-elapsed crosses
0.35 — 2026-07-23 12:48:00 EDT — after which the derived status returns to
allowed and suggestions resume with no user action, no restart, no settings
change.
Observed, via three unattended headless probes on the same machine and session:
| probe (EDT) |
derived status |
representative claim |
7d utilization |
prompt_suggestion emitted |
| 12:35 (before) |
allowed_warning |
seven_day |
0.70 |
no |
| 12:55 (after) |
allowed |
five_hour |
— |
yes |
| 13:30 (confirm) |
allowed |
five_hour |
— |
yes |
The derived status flipped across the predicted 12:48 boundary and suggestions
returned on the next turn — nothing on my end changed between 12:35 and 12:55.
That is the gate clearing itself as the pace rule stopped matching, exactly as the
code path predicts.
Note that 7d utilization actually rose from 0.62 (08:04) to 0.70 (12:35) over
the morning, yet suppression still cleared — because 0.70 never reached rule 1's
0.75 threshold, so only the {0.5, 0.35} rule was ever armed, and that rule is
released by elapsed time (crossing 0.35 of the window), not by usage falling. This
is the clearest possible confirmation that the trigger is pace, not proximity to
the limit: I was using more quota when it came back.
What I think should change
allowed_warning is a fully usable state — requests succeed. Suppressing on it
should be a deliberate, documented decision, not a strict-equality fall-through.
If it is deliberate, gate on rejected (or on the derived warning only above a
much higher utilization).
- If it stays, surface it. The suppression is completely silent — the reason
exists in telemetry (reason: "rate_limit") but is shown nowhere to the user.
Five closed issues here are people chasing env vars and settings for a state
the client already knows the reason for.
- Consider whether a pace heuristic — 62% used at day 2.3 of 7 — should govern
an ambient UI hint at all, when the header itself says allowed.
Tracked separately as a feature request in #72497.
Related: #57822 (same strict-equality class, fixed), #79919 and #77144 (may be
this gate or may be the separate enablement gate — see my comment there for a
one-command discriminator), #30699 (tengu_crystal_beam has zero occurrences in
2.1.218, so the budgetTokens: 0 theory there no longer applies).
Update 2026-07-23 — I have located the gate in the shipped binary, captured
the suppression live, and confirmed it with a pre-registered prediction (the
derived status cleared itself at the predicted time and suggestions resumed with
no user action — see "Prediction, then observation" below). Replacing my original
reproduction steps, which described only one of the two ways to reach the warned
state.
Root cause
From the 2.1.218 bundle (byte-identical in 2.1.216 and 2.1.217):
mie().statusis one ofallowed | allowed_warning | rejected. The strictinequality means the still-fully-usable
allowed_warningstate is treatedexactly like
rejected.Its only caller is the suggestion generator, which bails and emits telemetry:
Both entry points reach it, so this affects the TUI ghost text and the SDK
prompt_suggestionmessage identically:Mnd(e,t)— interactive,querySource.startsWith("repl_main_thread"), source"cli""sdk"Two routes into
allowed_warning(this is what my original report missed)Txu(headers)re-derives the status rather than passing the header through.When the header is
allowedorallowed_warning, it callszzg(); if thatreturns a warning object the derived status is
allowed_warning, otherwise it isnormalized down to
allowed.zzgwarns on either a-surpassed-thresholdheader, or this pace table:with
Gzg(resetsAt, windowSeconds)returning the fraction of the windowelapsed, and the rule being
utilization >= f.utilization && elapsedPct <= f.timePct.That is an ahead-of-pace test, not a near-the-limit test. On the weekly claim
it fires at 25% used in the first 15% of the week, 50% in the first 35%, or 75%
in the first 60%. A user who front-loads their week is suppressed for days while
every request still succeeds.
Live capture — 2026-07-23 08:04 EDT, v2.1.218, Linux
Server response headers on
/v1/messages:Client-derived state, from the stream-json
rate_limit_event:{"status":"allowed_warning","resetsAt":1785218400, "rateLimitType":"seven_day","utilization":0.62,"isUsingOverage":false}The server said
allowed. The client stamped itselfallowed_warningbecause0.62 >= 0.5 and only 0.3219 of the week had elapsed (window opened
2026-07-21 02:00 EDT).
Client telemetry from the same run (base64
additional_metadatadecoded):Enablement passed. The runtime gate suppressed with
reason: "rate_limit".No
prompt_suggestionmessage was emitted on any probe turn.For completeness, my earlier report was based on a June capture in which the
header itself read
allowed_warning— so both routes are real and both landon the same gate.
Reproduction — headless, no TUI, no waiting for a usage window
Two turns are required — the generator early-returns
early_conversationbelowtwo assistant messages. Then read the second run's output:
rate_limit_event.rate_limit_info.statusis the derived statusprompt_suggestionmessage present = fired; absent while the derived statusis
allowed_warning= suppressed by this gatePrediction, then observation (2026-07-23)
Ahead of time I predicted: with the 7d claim ahead of pace, the rule
{utilization:0.5, timePct:0.35}stops matching the moment window-elapsed crosses0.35 — 2026-07-23 12:48:00 EDT — after which the derived status returns to
allowedand suggestions resume with no user action, no restart, no settingschange.
Observed, via three unattended headless probes on the same machine and session:
prompt_suggestionemittedallowed_warningallowedallowedThe derived status flipped across the predicted 12:48 boundary and suggestions
returned on the next turn — nothing on my end changed between 12:35 and 12:55.
That is the gate clearing itself as the pace rule stopped matching, exactly as the
code path predicts.
Note that 7d utilization actually rose from 0.62 (08:04) to 0.70 (12:35) over
the morning, yet suppression still cleared — because 0.70 never reached rule 1's
0.75 threshold, so only the
{0.5, 0.35}rule was ever armed, and that rule isreleased by elapsed time (crossing 0.35 of the window), not by usage falling. This
is the clearest possible confirmation that the trigger is pace, not proximity to
the limit: I was using more quota when it came back.
What I think should change
allowed_warningis a fully usable state — requests succeed. Suppressing on itshould be a deliberate, documented decision, not a strict-equality fall-through.
If it is deliberate, gate on
rejected(or on the derived warning only above amuch higher utilization).
exists in telemetry (
reason: "rate_limit") but is shown nowhere to the user.Five closed issues here are people chasing env vars and settings for a state
the client already knows the reason for.
an ambient UI hint at all, when the header itself says
allowed.Tracked separately as a feature request in #72497.
Related: #57822 (same strict-equality class, fixed), #79919 and #77144 (may be
this gate or may be the separate enablement gate — see my comment there for a
one-command discriminator), #30699 (
tengu_crystal_beamhas zero occurrences in2.1.218, so the
budgetTokens: 0theory there no longer applies).