[BUG] Max 20x plan: 100% usage after 2 hours of light work (5 commits, no agents)
Open 💬 42 comments Opened Apr 1, 2026 by weilhalt
Description
Max 20x plan ($200/mo), hit 100% usage after approximately 2 hours today (April 1, 2026). This has never happened before with comparable workloads.
What I did
- 5 small commits (security tests, doc updates, test fixes)
- No subagents, no Agent tool usage
- Normal sequential workflow: read → edit → test → commit
- Model: Opus 4.6 (1M context)
Expected behavior
2 hours of light work should not exhaust the 5-hour Max 20x window. Previously, similar sessions used maybe 20-30% of the budget.
Environment
- Claude Code CLI on Linux
- Max 20x plan
- Model: claude-opus-4-6 (1M context)
Related issues
This appears to be the same underlying problem reported since ~March 23:
- #38335
- #38357
- #37394
- #40903
Something changed server-side in quota calculation around that date. The budget is being decremented far faster than actual token usage would justify.
42 Comments
Adding context for the Anthropic team:
I'm paying $200/month for the Max 20x plan specifically to have sufficient capacity for professional development work. Since approximately March 23, the same workloads that previously used 20-30% of my budget now exhaust it completely in under 2 hours.
This is not a minor inconvenience — it's a significant reduction in the service I'm paying for. I'd like to understand:
I rely on Claude Code for daily professional work and the current state makes the Max 20x plan poor value compared to what was delivered before March 23.
Thanks for your Issue! I had the same thing happen several times in the last few days, but on Max 5x I thought about switching to 20x but after your message I changed my mind, now I'm testing OpenAI Codex, let's see what happens. Although I really love Claude in its thought process
I've been having the same problem with Pro plan. Few small fixes and code refactors are quickly spending my budget in under two hours. A week ago the same amount of work (or even bigger changes) would never reach the usage limit.
Same issue! This is crazy. I pay 200 a month for this!
Same exact problem, I believe it might be related to this issue #34629 and they marked it as closed but there's someone commenting asking why they closed it if the issue persist.
The most insightful comment in that thread is the guy saying that
Anthropic better wake up any sleeping engineers and reset everyone's usage once this is fixed. 200 dollars is not a small chunk of money and I won't be waiting until next monday for my quota to reset.
Confirmed still broken on v2.1.89 (Windows, interactive +
--print --resume)Environment:
claude-opus-4-6[1m].exe(~/.local/bin/claude.exe)Clean reproduction
Ran from an empty directory with no
CLAUDE.mdor memory files to avoid tool-use turns muddying the results. All messages were single-turn (1 API call each), simple math questions.Results
| Message | cache_read | cache_creation | input_tokens | num_turns | cost |
|---------|-----------|----------------|-------------|-----------|------|
| 1 (fresh) | 11,272 | 4,885 | 2 | 1 | $0.036 |
| 2 (resume) | 11,272 | 4,900 | 2 | 1 | $0.036 |
| 3 (resume) | 11,272 | 4,915 | 2 | 1 | $0.036 |
cache_readis stuck at exactly 11,272 across all three messages — only the system prompt is being cached. The conversation history is re-created from scratch on every resume call (cache_creationgrows by ~15 tokens per message instead of shifting tocache_read).Expected behavior
cache_readshould grow as conversation accumulates (e.g. ~16k on msg 2, ~17k on msg 3), withcache_creationdropping to a small delta.I noticed that sometimes when it writes something like:
· Shimmying…
⎿ Tip: ...
It takes a long time and nothing happens, it's a really bad sign, after that my limits get eaten up by 30%
Our accounting dept is planning to initiate charge back requests with our bank for all bills covering the period of token burn. Sadly this is quickly crossing into civil liability territory. Silence is not golden in cases such as this.
claude --resume <id>20% for "2+2" on a resumed 605k session
I suspect Anthropic is trying to drive us to /clear much more often.
Same here. I'm on the $200/month max plan and all I was doing was reviewing plans and research and I hit my limit in 50 minutes. I mean this is really out of hand. It's ridiculous. Thanks for the issue.
btw yeah i also noticed that new sessions dont have this problem. It feels like sessions created before the update spam tokens under the hood and can be dangerous as they eat up limits
This was interesting
TLDR; This is what we are running as a workaround. It does not fix the single, large hit that comes from resuming a long context, but it does seem to stop the bleeding otherwise. It is based on the work of others (see the end) as well as Claude itself. YMMV.
The Problem
Two bugs in Claude Code v2.1.69+ cause prompt cache misses, leading to ~20x cost increases:
--resume, attachment blocks (skills, MCP, deferred tools) are injected at the END of the message array instead of the BEGINNING. This breaks the cache prefix match — every turn rebuilds the full context from scratch instead of reading from cache.cc_versionfingerprint in the attribution header is computed from meta/attachment content instead of real user input. When attachment content changes between turns, the fingerprint changes → system prompt changes → cache bust. Additionally, tool schema ordering is non-deterministic, causing further cache invalidation.How the Fix Works
A Node.js fetch interceptor (
--importpreload) runs between Claude Code and the Anthropic API. On every/v1/messagesrequest, it:messages[0]on every API call, matching fresh session layoutcch=00000) by using the npm package instead of the standalone binaryInstallation
Prerequisites
node --version)npm --version)Step 1: Install the Claude Code npm package
The standalone binary has a Zig-level HTTP stack that cannot be intercepted from JavaScript. The npm package uses Node.js
globalThis.fetch, which we can intercept.Step 2: Create the fetch interceptor
Save the following as
~/.claude/cache-fix-preload.mjs:<details>
<summary>Click to expand cache-fix-preload.mjs</summary>
</details>
Step 3: Create the wrapper script
Save the following as
~/bin/claudeand make it executable:Step 4: Ensure
~/bintakes PATH priorityThe Claude Code auto-updater overwrites
~/.local/bin/claudeon every update. Placing our wrapper in~/bin(which takes PATH priority) ensures it survives.Step 5: Verify
What to Expect
| Scenario | Cost |
|----------|------|
| Fresh session, first turn | Normal (small context) |
| Fresh session, subsequent turns | ~0% (cache hit) |
| Resume, first turn | Expensive — proportional to session size (cold start
cache_create) || Resume, subsequent turns | ~0% (cache hit) |
| Long idle then next turn | Same as resume first turn (cache expired server-side) |
Verified Results (2026-04-01)
Tested on a resumed 605k token session (2400+ messages):
| Turn | Usage jump | Cache behavior |
|------|-----------|----------------|
| Resume (first turn) | ~14% |
cache_create— cold start, expected || Second turn ("What is 3+3?") | 0% |
cache_read— fix confirmed working || Ongoing active turns | <1% |
cache_read— stable |Cost Management Tips
/compactbefore stepping away. Shrinks context so the next cold start (from idle or resume) is cheap.Updating
When Claude Code releases a new version, update the npm package:
The wrapper script and preload interceptor don't need changes unless Claude Code significantly changes its message structure.
Reverting
To go back to the unpatched standalone binary:
Limitations
cache_createtax proportional to session size. This is an API-level behavior, not something fixable client-side.~/.local/bin/claude.Credits
messages[0]asymmetryThanks to @cnighswonger for the excellent preload interceptor approach. While testing on v2.1.90, we found three issues and applied fixes.
TL;DR
v2.1.89+ changed internal message normalization, causing partial attachment scatter on resume — some blocks stay in
messages[0], others drift.Findings (via
CACHE_FIX_DEBUG=1)Fresh session
messages[0]:[deferred_tools, MCP, skills, context, user_text]Resume session:
Script logged
SKIPPEDbecausefirstAlreadyHassaw deferred+MCP and bailed early.Three Fixes Applied
1. Detection: partial scatter missed
Also changed scan range to
i >= firstUserIdx(include first msg), then remove all relocatable from all messages and rebuild — handles both full and partial scatter.2. ORDER doesn't match fresh session
Fresh session order is
deferred → mcp → skillson v2.1.90.3. False positive on quoted content
"Note:" file-change reminders embed file contents. If debug log text contains
"deferred tools", the.includes()matcher triggers on the quoted content, corruptingmessages[0].Same
startsWithfor skills and MCP. Hooks keepssubstring(0, 200).includes()since we never observed an actual hooks block in practice.My Edit
<details>
<summary>Click to expand cache-fix-preload.mjs</summary>
</details>
Notes
cache_creation ≈ 7,400on both fresh and first resume)SIY()function (source'sextractFirstMessageText) picks the firsttype==="text"block from the firstusermessage — whenmessages[0]starts with a<system-reminder>relocatable block, that block's text becomes the fingerprint input instead of the real user text, which is exactly whystabilizeFingerprint()is neededhooksblocks never appeared in any test despite hooks being configured and firing (PostToolUsehooks with Edit/Write matcher across multiple sessions). TheisHooksBlockmatcher is retained as defensive code with a conservativesubstring(0, 200)check since we have no real sample to define astartsWithpatternFINGERPRINT_SALT = "59cf53e54c78",FINGERPRINT_INDICES = [4, 7, 20]) verified unchanged in v2.1.90 source (RIY,[4,7,20], SHA256 slice(0,3))"The following deferred tools are no longer available (their MCP server disconnected...)"system-reminder may appear alongside scattered blocks. ThestartsWithmatcher correctly ignores it ("are no longer available"≠"are now available"), whereas the old.includes("deferred tools")would have false-positive matched it — relocating a resume-only block intomessages[0]would break the cache prefix since fresh sessions never contain itThese fixes have had a massive impact on stopping the traumatic token bleeds we were experiencing. Thanks to @jmarianski, @VictorSun92, and all who are testing this fix.
@cnighswonger I've read your original message, couple hours ago wanted to point out that your findings aligned with mine. After tests right now I was able to ALSO confirm @VictorSun92 new findings, as if version 2.1.90 suddenly changed during the day.
I've made a script that was supposed to check the findings align, however... I can't, cause there are server issues that prevent identical requests from being cached :D And even discounting the tools swapping positions.
Anyway, here's the script. Supposedly it should work for both -p mode and regular /resume. However, currently it is broken 100% of time.
test_resume_cache.sh
Mind you, if you continue session further cache will somehow start working. No clue why, perhaps server cache needs a while to adapt to sessions being sent from the same computer?
@jmarianski Thanks for the analyzer script. It is quite helpful. Hopefully Anthropic will get the right fix in place before too much longer. In the meantime, I worked with Claude and Codex (GPT) to debug the "broken 100% of the time" issue. Here is what we discovered and the solution we arrived at which seems to test out on our end. The revised script is attached:
test_resume_cache.sh
Why the current script shows
BROKENIn
print,interactive, andslashmode, each resume phase starts a new Claude Code process, sends one prompt, and exits. That means the phase summary is mostly judging the first call made by each resumed process.That is important and useful data, but it does not show whether cache reads begin to recover on subsequent API calls after the resumed process is already active.
What
multimode addsTo measure that second effect, we added a
multimode to your script. Instead of sending one message and exiting, it sends multiple messages within the same resumed process and then inspects howcache_readandcache_creationchange across those calls.That gives us a way to separate:
What our run suggests
Using:
we saw:
The first resumed call still looks cold, but later calls in the same resumed process show
cache_readincreasing andcache_creationdropping. That suggests some cache recovery can happen after the resumed process is already in flight, even when the transition summary still marks the resume boundary asBROKEN.That's not intended to be a firm root-cause conclusion: only a suggestion.
Suggested interpretation
I think the safest takeaway is:
One analyzer caveat
One thing I would keep in mind is that the current phase grouping assumes API calls divide fairly evenly across phases. That is reasonable for single-call modes, but it gets less precise in
multimode because one process can produce a variable number of API calls depending on tool use and turn shape.The
multirun is useful evidence that later within-process behavior may improve, but not proof of a complete explanation by itself.Usage
Note on the matcher fix
One update in the revised interceptor still looks especially important: switching the deferred-tools matcher away from a broad
.includes("deferred tools")check. On resume, a"The following deferred tools are no longer available..."reminder can appear, and a broad substring match can treat that as a relocatable block when it is actually resume-only state.Using a stricter header match avoids pulling that reminder into
messages[0], which would make the fresh vs resumed prefixes diverge unnecessarily.Thats bonkers @cnighswonger ! However, the main point of my script was to catch if the resumption breaks cache, not if the next messages in resume chain can work. As I've seen they do work, so it wasn't part of my research. However it's great to have it like that.
@jmarianski Apologies for the over-focused response. 🤪
We are thinking that the cache entries from the fresh context either expire (TTL) or are scoped to that connection and simply gone on a resume. But that's a guess based on noticing that allowing a context to sit open and idle for some length of time and then turning it once results in a similar token bleed as a full exit/resume. We've not done any testing to try to determine the effective TTL of an idle context.
We also think that this "feature" may have always been present, but has now become amplified by the 1M token context "enhancement." Here is a bit of a convo with the Claude agent we have working this issue:
Update: I built a monitoring tool for this problem.
After dealing with the budget drain for weeks, I built BudMon — a real-time desktop dashboard that captures rate-limit headers from Claude Code API responses and visualizes quota utilization, burn rate, and costs.
What it shows:
It works by loading a read-only Node.js fetch interceptor via
NODE_OPTIONS— captures response headers and SSE token usage, never modifies requests. No API keys needed, all data stays local.Install:
pip install budmon && budmon --setupGPL-3.0, Python 3.10+, tkinter, no external dependencies.
This doesn't fix the underlying budget drain, but at least you can see what's happening in real-time instead of being surprised by "quota exhausted".
@cnighswonger Just a reminder, there is no session per-se in CC, as all the requests are HTTP by definition. It can be maintained by some form of headers, cookies or simply hashing part of the body, but requests are stateless in nature. So no scoping by connection. Unless they also simulate connections by this one haiku request on subsequent reconnects... Either way, no way of knowing.
@jmarianski Right, but prompt caching is in play I think. (See here.) Our apps that interact with the API are very prompt-caching aware in an effort to minimize token burn. It appears that multiple bugs in Claude Code are breaking the prompt-caching. The doc referenced also states there is either a 5 min TTL or 1 hr TTL depending on tier. In practice we're not sure exactly what it is at this point. Maybe those numbers are correct.
From the doc it appears that there are two constraints on the prompt: organization and turn time. So cross-process cache sharing should work within the 5-minute (or 1-hour) TTL if the prefix is identical.
I may not be on the same page, so please feel free to let me know if that's the case. Our discussion has been a helpful learning process for me in many ways.
For kicks I had an agent run an analysis of the cache performance of an ongoing, lengthy conversation of another agent that is doing code work on the UI of an app we developed and maintain. All of our agents are running the interceptor described above with @jmarianski enhancements. Here are the results (using the previously referenced prompt-cache doc):
Confirming this issue — with data from prompt cache analysis.
Max 5x plan, Windows, Opus 4.6 (1M context), v2.1.92.
I tracked
ephemeral_1h_input_tokensandephemeral_5m_input_tokensacross ~50 recent sessions. The 1-hour prompt cache TTL silently stopped working around April 1:| Date | Version | 1H cache hits | 5M cache creates | Status |
|--------------|------------|----------------------|------------------|---------------------------|
| Mar 31 | v2.1.86 | 206/212 calls | 0 | ✅ Normal |
| Apr 1 03:36 | v2.1.86 | 206/212 calls | 0 | ✅ Last working session |
| Apr 1 10:57 | v2.1.86 | 0/14 calls | 14 | ❌ Broken (same version!) |
| Apr 1–4 | v2.1.86→92 | 0 across 39 sessions | All 5M | ❌ Never recovered |
Key finding: the version didn't change at the transition point. Both the last working session and the first broken session were on v2.1.86. This is a server-side change, not a client regression.
The 1H TTL appears to be gated server-side. It looks like the gating was modified around April 1, effectively removing users from the 1H cache tier. Without 1H TTL, every gap >5 minutes triggers a full cache rebuild of the system prompt (~160K tokens), at roughly 12.5x the cost of a cache read.
This directly explains the sudden quota burn many of us are experiencing — same workload, same version, but cache efficiency dropped dramatically overnight.
@TigerKay1926 Have you checked your feature flags? If you proxy your request you will be able to do so. I'm currently nowhere near a working pc, so I can't check, but from my research the 1h cache is preserved (you will notice you can continue conversation without turning application off (after you've stopped speaking for at least 5 minutes) and you will still not get additional costs).
Additional costs do apply after you relaunch application and context needs to be rebuilt, even if less than 5 minutes has elapsed between calls. However I haven't tested since 2.1.90.
@TigerKay1926 This is the missing piece. Our cache performance analysis (posted above) showed 98.2% hit ratio with busts occurring exclusively on time gaps — we attributed it to the 5-minute ephemeral TTL being the operative constraint but couldn't explain why the 1-hour tier wasn't covering the system prompt prefix. Your data pinpointing the tengu_prompt_cache_1h_config flag change on April 1 answers that directly.
It also explains our earlier observation that the baseline on cold starts was ~6,500–8,800 tokens — that was the system prompt + tools that used to stay on the 1-hour TTL. Now they expire with everything else at 5 minutes.
For our use cases, we've been mitigating with:
• /compact before idle periods exceeding ~5 minutes
• Cron intervals ≤4 minutes for background agents to keep cache warm between fires
If the 1-hour tier is restored, most of the operational overhead disappears. If it isn't, the 5-minute window becomes a permanent constraint for anyone running long or periodic sessions.
@jmarianski @TigerKay1926
Follow-up after checking our own data more carefully.
@jmarianski is right — the 1-hour TTL is still active on our account. We checked the
cache_creationbreakdown across three active sessions and 100% ofcache_creation_input_tokensare categorized asephemeral_1h_input_tokens, zeroephemeral_5m. The usage object includes the breakdown:Our cold starts correlate with gaps >60–80 minutes — consistent with a 1-hour TTL, not 5 minutes:
| Gap | cr% | cr tokens | Notes |
| ------ | ----- | --------- | ------------------------------ |
| 153.6m | 91.3% | 196,374 | >1h — TTL expired |
| 76.8m | 69.4% | 42,518 | Just past 1h boundary |
| 164.5m | 89.4% | 157,540 | >1h |
| 18.3m | 44.7% | 17,041 | Process restart (power outage) |
| 168.3m | 40.7% | 14,437 | >1h |
| 630.5m | 43.3% | 16,049 | Overnight idle |
| 79.1m | 100% | 39,550 | Just past 1h boundary |
Every real bust is at 77+ minutes. The 18-minute outlier was a process restart, not TTL expiry.
So the picture may be this: @TigerKay1926's account was removed from the 1-hour tier via the
tengu_prompt_cache_1h_configflag, but it hasn't been removed universally. This makes the impact uneven across users — some are on 1h TTL and experiencing normal cache behavior, others were downgraded to 5m and seeing the dramatic quota burn.Our previous reply's mitigation advice (cron ≤4min, /compact before idle) applies specifically to the 5-minute TTL scenario. For those still on the 1-hour tier, the effective idle threshold before a cold start is ~60 minutes, which is far more manageable.
FYI minified code also had hints that on "extra usage" TTL of cache is 5 minutes. Perhaps that's also where the distinction may come from between users.
Indeed... we are looking at that just now. It triggers an insane burn rate. More in a few.
New finding: exceeding 100% of the 5-hour quota triggers a TTL downgrade from 1h to 5m.
We observed this live today. While our account normally operates on the 1-hour TTL tier (all
cache_creationtokens categorized asephemeral_1h_input_tokens), when we crossed 100% of the 5-hour quota window, the API response immediately switched to reportingephemeral_5m_input_tokensexclusively — noephemeral_1htokens at all.This is a double penalty:
The transition happens at the exact quota boundary. Once the 5-hour window resets and utilization drops back below 100%, the 1h TTL returns.
We detect this dynamically by checking the
cache_creationsub-object in the usage response. If you're on the 1h tier and suddenly seeing 5-minute cache behavior, check youranthropic-ratelimit-unified-5h-utilizationheader — you may have crossed into overage.This makes quota management even more critical for Max plan users: hitting overage doesn't just cost more per token, it actively degrades your caching efficiency, creating a runaway feedback loop.
The $100 Extra Usage credit gesture was nice, but we didn't even feel it. It bought us about 30 min of usage. On our subscription, it buys us a month.
TLDR; Cache management matters and should be a user's top priority.
A few moments ago one of our agents busted cache and did not notice it. By the time we noticed it we went from 0% usage for the current 5hr limit to 70%. I think it was about three or four turns. When queried about the burn, the agent caught itself and compacted immediately. Burn rate returned to "normal."
Follow-up on the overage TTL downgrade — we found the mechanism, and confirmed it's server-enforced.
Anthropic's Extra Usage FAQ states that overage billing uses "standard API rates." And the prompt caching pricing table has a footnote: "Prompt caching pricing reflects 5-minute TTL."
So the TTL downgrade from 1h to 5m at the 100% quota boundary isn't a separate penalty — it's a side effect of being routed to the standard API billing path, which only supports 5-minute cache TTL. The 1-hour TTL is a subscription-tier feature that doesn't carry over into Extra Usage.
We tested whether client-side TTL injection could override this. Claude Code already sends
{"type": "ephemeral", "ttl": "1h"}on all itscache_controlblocks. At 99% quota, the API honors it — response showsephemeral_1h_input_tokensonly. At 100% quota, the API silently ignores the"ttl": "1h"request and returnsephemeral_5m_input_tokensonly. The downgrade is server-side and not client-controllable.This also reveals a pricing difference most users won't notice:
| TTL Tier | Cache Write Cost (Opus 4.6) | Multiplier |
| -------- | --------------------------- | ---------- |
| 5-minute | $6.25/MTok | 1.25x base |
| 1-hour | $10.00/MTok | 2.00x base |
So in-quota subscription users are paying 60% more per cache write token for the 1h TTL — but getting dramatically fewer cold starts. On Extra Usage, you pay the lower 5m write rate but write far more often because the cache expires 12x faster.
For a 200k token context, the math works out roughly:
The runaway feedback loop is now fully explained: overage → standard API path → 5m TTL → frequent cache rebuilds → more tokens burned → deeper into overage. There is no client-side mitigation for the TTL downgrade itself — the only defense is staying under 100% quota.
This once again stresses the need to understand Anthropic's caching mechanism as well as their billing policy in order to maximize cost effectiveness.
Its not even clear that Anthropic understands how it works in light of the massive Claude Code failures of the past month(s). They are probably bleeding cash at a rate that requires near real time price algo adjustments due to the massive COGS associated with AI operations at this point in history. Success can be unsupportable. Just sayin.
100% after 2 hours of light work means session bloat is inflating every API call. Cozempic v1.6.11's guard daemon keeps context lean with 4-tier pruning — 18 strategies strip progress ticks, stale tool results, thinking blocks, and other dead weight before each turn.
pip install cozempic && cozempic initUpdate: 7 consecutive days, 344+ API calls, zero
ephemeral_1hhits — Max 5x planFollowing up on my 2026-04-04 comment with fresh data after the weekly quota refresh.
@cnighswonger — thank you for isolating the overage-downgrade mechanism. Your finding that crossing 100% of the 5h quota routes requests to the standard API billing path (5m TTL only) is by far the clearest explanation posted in this thread, and the prompt-caching pricing footnote you cited seals it.
But my account presents a case the mechanism does not fully explain.
Data (Max 5x, Opus 4.6 1M context, CC v2.1.89-2.1.93, Windows)
| Date (UTC) | API calls |
ephemeral_1hhits |ephemeral_5mhits | 5h quota state ||---|---|---|---|---|
| 2026-04-01 (before 10:57 UTC) | 212 | 206 | 6 | in-quota |
| 2026-04-01 (after 10:57 UTC) | ~80 | 0 | all | in-quota |
| 2026-04-02 to 04-06 | ~500 | 0 | all | mostly in-quota |
| 2026-04-07 (weekly quota refresh day) | 344 | 0 | 344 | fresh 0% quota at window start |
Between 2026-04-01 and today, I have not observed a single non-zero
ephemeral_1h_input_tokensvalue. Not once across ~900 calls. The 5m tier itself is working normally (38 KB+ cache hits inside 5-minute windows), so this is not a caching outage — it is specifically the 1h tier that is gone on this account.2026-04-07 is the critical data point. If the overage-downgrade were the only factor, a fresh weekly quota window starting at 0% utilization should have restored the 1h TTL immediately. It did not. 344 calls into the new cycle, still 0/344.
Questions for Anthropic
{"type": "ephemeral", "ttl": "1h"}incache_controlblocks and then ignore it server-side, with no error, no warning header, no documented downgrade conditions? The downgrade mechanism had to be reverse-engineered from the usage object by users in this thread because no official documentation of it exists.Cost impact on Max 5x
With a ~160k token system prompt being rebuilt every 5 minutes instead of every hour, a single active hour burns roughly 12x the
cache_creationtokens the 1h tier would. On Max 5x, that is the difference between a usable workday and hitting the weekly cap within two days. The $100 Extra Usage credit that was issued to some affected users covered about 12 Opus turns on my account — that is a rounding error, not remediation.This thread is now at 32 comments, multiple independent reverse-engineering efforts by users, and zero official responses since the first report on 2026-04-01. It has been six days. Please respond. /cc @anthropics maintainers.
Adding our data from another Max 5x account — our experience with the TTL transition has been different, which may point to more than one mechanism at play.
We've observed the 1h ↔ 5m transition in both directions, tied cleanly to the 5h quota boundary:
On 2026-04-04, we tested this directly. At 99% of the 5h quota, the API honored
ttl: "1h"and returnedephemeral_1h_input_tokens. At 100%, it silently switched toephemeral_5m_input_tokensonly. When the 5h window rolled and utilization dropped back below 100%, the 1h TTL returned. We've crossed back and forth multiple times since then — the transition has been immediate and reversible every time on our account.Session-level data across multi-day sim runs:
| Session | 5h Quota State | Cache Write TTL | Cache Hit Rate |
|---|---|---|---|
| Apr 3 (15h run) | Over 100% | All
ephemeral_5m| 5.3% || Apr 5 (8h run) | Under 100% | All
ephemeral_1h| 19.7% |Same codebase, same account, same plan. The Apr 5 session on 1h TTL had 6 of 8 Sonnet hours with zero cache writes (pure cache reads), consistent with the 1h TTL working as intended. We've also validated our steady-state 1h behavior independently — cold starts correlate at the 77+ minute idle boundary, never at 5 minutes.
This suggests there may be more than one mechanism controlling TTL assignment. The quota-driven downgrade we've documented is one — but your Apr 7 data (0/344
ephemeral_1hon a fresh 0% weekly quota) clearly rules that out as the sole explanation for your account. Something else is holding your account on the 5m tier, independent of quota state. It could be an account-level flag, a configuration change that coincided with your Apr 1 cutoff, or a mechanism we haven't identified yet. The fact that our accounts behave differently under similar conditions makes the case that this isn't a single root cause.A note on documentation: We checked the prompt caching docs, pricing page, service tiers, and Extra Usage FAQ. None of them document any conditions under which the 1h TTL is downgraded, restricted by plan, or otherwise unavailable. The API silently accepts
{"type": "ephemeral", "ttl": "1h"}and ignores it server-side with no error or warning. Everything in this thread about the downgrade mechanism has been reverse-engineered from the usage response object by users.@claude @anthropic we're waiting, patiently. Downgraded my account until it's fixed. Issue some type of acknowledgement, please.
Yeah this lines up with what a few people have been noticing lately.
Once you cross that usage threshold, it’s not just “more usage”, the system actually shifts how caching behaves (shorter TTL), so the same prompts stop benefiting from reuse and effectively get reprocessed more often.
That’s why it suddenly feels like things are burning through quota way faster even with similar workflows.
Have you noticed if it ramps up right after hitting 100%, or does it feel gradual?
Follow-up: confirmed bidirectional TTL transition on window reset (Apr 8)
One of our agents briefly crossed 100% Q5h today. Extra usage balance dropped $0.07. When the 5h window reset moments later, Q5h went to 0% and TTL immediately recovered to 1h — confirmed via our interceptor's new TTL tier detection (
ephemeral_1h_input_tokensfrom the response usage object).This appears to be the opposite of @TigerKay1926 Apr 7 experience where 344 calls on a fresh 0% quota produced zero
ephemeral_1hhits. Same plan (Max 5x), same week. To this point, our account has always shown immediate bidirectional transitions at the quota boundary, whereas theirs has been stuck on 5m since Apr 1 regardless of quota state.At this point the data strongly suggests there's account-level state beyond quota utilization controlling TTL assignment. Whether it's a flag, a config cohort, or a server-side routing decision, it's not something visible to us in the API response.
Guys, I just uninstalled the native installer and reinstalled using the npm package.
I also asked Claude Code to write a script to log cache activity. From what I can see, caching works properly in the npm version. I had already noticed cache issues in the native version before, so this seems consistent.
Also, another user wrote a script to enforce the 1-hour cache. You can run it with:
node claude-1h-cache-patch.jsNote: this only works with the npm version.
claude-1h-cache-patch.js
@Vergil824 Good to see independent confirmation that npm works better than the standalone binary for caching. The standalone has Zig-level attestation that bypasses Node.js entirely, which means no client-side cache fixes are possible on that path.
Your 1h cache patch is doing similar work to our claude-code-cache-fix interceptor (
npm install -g claude-code-cache-fix), which also enforces cache stability but additionally fixes block scatter on resume, fingerprint instability, tool reordering, and image carry-forward stripping. v1.5.1 also includes a cost report tool for quantifying the savings.The cache log output you're showing is useful data — the progression from no cache activity on call 1 to 1h cache active on call 2 is exactly the expected behavior when the prefix stabilizes.
Further diagnostic data — account-level flag dump suggests non-empty allowlist with a scope mismatch
Following up on my earlier comments. After installing @cnighswonger's
claude-code-cache-fixinterceptor (npm:claude-code-cache-fix@1.5.1), I was able to log the feature flag state my account receives from the backend on first API call. Sharing the relevant portion in case it helps others diagnose:My account still registers zero
ephemeral_1h_input_tokensorganically, despite the allowlist not being empty. I had previously assumed the allowlist was being pushed empty to my account, which would explain the extended dry spell. That hypothesis appears wrong — the allowlist is populated with three patterns.A more consistent explanation is that the
querySource/scopevalue Claude Code passes on interactive CLI sessions doesn't match any of these three patterns:repl_main_thread*— prefix suggests SDK REPL contextssdk— literal SDK callsauto_mode— likely non-interactive automation pathsIf interactive CLI sessions route through a
querySourcevalue that isn't in the allowlist, the gating function returnsfalseand the client never requeststtl: "1h"on the wire. From the outside this looks indistinguishable from a server-side downgrade in the usage object.Questions for @anthropic:
querySource/scopevalue does Claude Code pass on interactive CLI sessions (i.e.,claudeinvoked directly, notclaude -p "...", not via SDK)?This would also help explain the diverging reports in this thread from Max 5x users — workflows that happen to route through
sdkorrepl_main_thread*would match the allowlist, while interactive CLI users would fall outside it entirely.Separately: thanks to @cnighswonger for the interceptor. The flag dump feature surfaces backend state that would otherwise be opaque, and end users should not need a third-party tool to understand why their cache is broken.
@TigerKay1926 Excellent diagnostic work — the allowlist discovery is a significant find.
We've confirmed the gating is entirely client-side. From the source (
src/services/api/claude.ts), theshould1hCacheTTL()function checks yourquerySourceagainst the GrowthBook allowlist patterns. If it doesn't match, the client simply omitsttl: "1h"from thecache_controlblocks — the request goes out as{"type": "ephemeral"}which the server defaults to 5m.The server honors whatever TTL the client requests. So the fix is straightforward: inject
ttl: "1h"on anycache_controlblock that hastype: "ephemeral"but is missing thettlfield.This is now live in v1.6.0 of our interceptor:
After updating, you should see
ephemeral_1h_input_tokens > 0in your cache stats. The debug log (CACHE_FIX_DEBUG=1) will showAPPLIED: 1h TTL injected on N cache_control block(s)when the fix fires.cc @Vergil824 @jmarianski @bilby91 — if you've been seeing 5m TTL despite being under quota, this may be the same issue. The interceptor now enforces 1h TTL regardless of the client-side gating.
Tagging @TigerKay1926 because I think this lands directly on your "stuck 5m TTL" observation.
We dumped the full tool list from Claude Code v2.1.101's outgoing API requests today during a cross-version regression investigation. The new
ScheduleWakeuptool added in v2.1.101 has this in its description, verbatim:That's Anthropic's own product tooling confirming that the 5-minute TTL is the baseline — and building advice to its own sub-agents around that constraint. Your observation about being stuck on 5m isn't a mystery or a weird edge case: it's the default behavior the design is built around. The 1-hour TTL tier exists but is opt-in via
should1hCacheTTL()+ the GrowthBook allowlist you identified. If yourquerySourceisn't in the allowlist, you get the 5m default.This matches exactly what you've been reporting. Wanted to make sure you saw it confirmed from the other side.
A fuller writeup, including a cross-version measurement table (v2.1.81 → v2.1.83 → v2.1.90 → v2.1.101) and the release-timing argument that the March 23 regression is server-side rather than client-side, is at https://veritassuperaitsolutions.com/5-minute-baseline-tools-array/ — the ScheduleWakeup quote is one datum in a broader investigation we ran today. If you're interested, the per-version prefix size table there shows exactly where the 5m/1h gating starts mattering.
Thanks for the original GrowthBook allowlist analysis. It holds up completely against what we measured today, and it was the lead that made this investigation possible.