Repository navigation
Description
Summary
Resuming a session with --continue or /resume completely invalidates the prompt cache, even when re-entering within seconds. On a long session with a large prompt, this forces a full cache_creation of 400-500k tokens each time — silently burning through rate limits.
What happened
On a 1M context Opus 4.6 session with ~500k prompt, I exited and re-entered with --continue a few times. Each time, cache_read dropped to 0 and the entire prompt was re-cached from scratch — even though the re-entry happened within 2-3 seconds (well within 5-minute cache TTL).
Additionally, the prompt size barely changed after re-entry, meaning whatever restructuring happened during reload provided no benefit — it just destroyed the cache.
All resume methods affected (controlled tests)
Tested with the same session, varying only the resume method and flags:
Test 1: --continue --dangerously-skip-permissions (12s gap)
Before: cr: 172,301 cc: 1,344
After: cr: 0 cc: 174,210 ← full re-cache
Test 2: --continue only (20s gap)
Before: cr: 176,193 cc: 0
After: cr: 0 cc: 176,730 ← full re-cache
Test 3: /resume (interactive resume, ~2.5h gap)
Before: cr: 160,149 cc: 314
After: cr: 0 cc: 156,682 ← full re-cache
All three: cache_read: 0, full re-cache. The --dangerously-skip-permissions flag is irrelevant — it's the resume/continue mechanism itself.
Raw Data (deduplicated by msg_id, from JSONL session log)
Spike 1: session resume after ~4h sleep (expected)
04:18:37 cr: 389,000 cc: 210 out: 640 stop:end_turn ← last turn before sleep
... ~4 hours gap (cache TTL expired — this one is expected)
08:08:09 cr: 0 cc: 0 out: 0 stop:stop_sequence ← session resume
08:09:47 cr: 0 cc: 367,175 out: 822 stop:tool_use ← 367k full re-cache (expected)
Spike 2: /exit → --continue, 2 seconds apart
09:04:15 cr: 499,517 cc: 486 out: 521 stop:end_turn ← last turn before /exit
... /exit at 09:04:29, --continue at 09:04:31 (2 SECONDS)
09:04:31 cr: 0 cc: 0 out: 0 stop:stop_sequence ← session reload
09:05:13 cr: 26,691 cc: 470,381 out: 1,352 stop:tool_use ← 470k re-cache! (WHY?)
Spike 3: /exit → --continue, 3 seconds apart
09:40:19 cr: 561,082 cc: 987 out: 289 stop:end_turn ← last turn before /exit
... /exit at 09:40:21, --continue at 09:40:24 (3 SECONDS)
09:40:24 cr: 0 cc: 0 out: 0 stop:stop_sequence ← session reload
09:42:59 cr: 26,691 cc: 511,847 out: 1,649 stop:tool_use ← 512k re-cache! (WHY?)
Messages in JSONL right before each spike
Spike 2 context:
/exit command logged
file-history-snapshot
custom-title
agent-name
file-history-snapshot
→ stop_sequence "No response requested."
→ cr:0 cc:470,381
Spike 3 context:
/exit command logged
file-history-snapshot
custom-title
agent-name
file-history-snapshot
→ stop_sequence "No response requested."
→ cr:0 cc:511,847
Before/after comparison
Before /exit → After --continue (first turn)
prompt: 500k → 497k (-0.6%) cache_create: 470,381 (entire prompt re-cached)
prompt: 561k → 538k (-4%) cache_create: 511,847 (entire prompt re-cached)
Impact
In a 2.6-hour morning window:
- 3 session resumes consumed ~1.43M cache_creation tokens
- Actual useful output during same period: ~155k tokens
- cache_creation was 9x the actual output
- Hourly rate limit cost: 920k/hr vs normal 412k/hr (2.2x increase)
Root Cause
Per @ArkNill's analysis: the deferred_tools_delta feature introduced in v2.1.69 reorders tool results when reconstructing the conversation for resume, which breaks the cache prefix and forces a full re-cache. See #34629 and https://github.com/ArkNill/claude-code-cache-analysis.
Environment
- Model: claude-opus-4-6 (1M context)
- Platform: macOS
- Multiple plugins/skills (~40k system prompt tokens)
- Session duration: ~34 hours (long-running)
Related
- [BUG] Prompt cache regression in --print --resume since v2.1.69(?): cache_read never grows, ~20x cost increase #34629 — Prompt cache regression in --print --resume since v2.1.69
- [BUG] Conversation history invalidated on subsequent turns #40524 — Conversation history invalidated on subsequent turns
- [BUG] Aggressive clearing of conversation history at ~2k tokens on Opus 4.6 (1M context) v2.1.89 #42136 — Aggressive context compaction on 1M context
- [BUG] Abnormal / inflated rate limit / session usage #38350 — Abnormal rate limit consumption
Activity
[-]Autocompact triggers at 43-56% context usage, invalidates cache, causes massive rate limit consumption[/-][+]Session resume (--continue) invalidates entire prompt cache, causes massive rate limit consumption[/+]ArkNill commentedon Apr 2, 2026
Your JSONL analysis confirms exactly what I've been tracking across multiple reports. The root cause is the
deferred_tools_deltafeature introduced in v2.1.69 — it reorders tool results when reconstructing the conversation for resume, which breaks the cache prefix and forces a full re-cache. This is the same mechanism behind #34629.Your Spike 2 and Spike 3 data (2-3 second exit→resume gaps with 470-512K re-cache) are particularly damning evidence, since the 5-minute cache TTL should have trivially covered that interval.
Compounding factor worth noting
This interacts badly with thinking signatures (#42260). When resumed, base64-encoded thinking blocks from prior turns get replayed as input tokens. Combined with zero cache hits from the prefix break, a 400+ message session can easily send 500K+ tokens as fully-billed input on a single resumed turn — effectively a token bomb.
What I'd recommend until this is fixed
--resume/--continueentirely for any non-trivial session. Starting fresh with a well-structured CLAUDE.md to restore context is dramatically cheaper.cache_read_input_tokensfield in your JSONL logs is the key diagnostic — any resume that shows near-zero cache read with high cache creation is hitting this bug.I patched v2.1.81 to fix the cache prefix handling and verified cache read ratios returned to 89-99% on fresh sessions. The broader cache analysis (including this resume-specific issue) is documented at https://github.com/ArkNill/claude-code-cache-analysis.
taekim34 commentedon Apr 2, 2026
Thanks @ArkNill for the
deferred_tools_deltaroot cause analysis and the link to your cache analysis repo. Very helpful!I've updated the issue body with additional test results confirming
/resumealso triggers the same cache invalidation.ArkNill commentedon Apr 2, 2026
Update (April 2): v2.1.90 fixes the main cache regression but this specific issue (
--resume/ session replay) remains. Avoid--resumeentirely — fresh sessions with a structured CLAUDE.md are the workaround.For non-resume usage, v2.1.90 now achieves 95-99% cache read on both npm and standalone. Details: https://github.com/ArkNill/claude-code-cache-analysis
simpolism commentedon Apr 2, 2026
2.1.90 has this line in the changelog:
So I assume that was another bug? Because I cannot use the harnesses I built around
claude -p --resumegiven the reported bug here. They eat my entire quota within a few turns.ArkNill commentedon Apr 3, 2026
April 3 update: The cache regression (Bugs 1-2) is fixed in v2.1.91. However, systematic proxy testing revealed additional unfixed mechanisms — a 200K tool result budget cap, a client-side false rate limiter (151 synthetic entries found), and silent microcompact clearing (327 events). Anthropic responded on X (Lydia Hallie) acknowledging peak-hour tightening but stating "none were over-charging you" — our measured data shows mechanisms their statement does not cover. Full analysis: claude-code-cache-analysis
ArkNill commentedon Apr 3, 2026
Received a patch via email from @simpolism that addresses the remaining
--resumecache misses on v2.1.91. Tested it locally through a monitoring proxy — results are solid.The patch targets two additional root causes beyond what v2.1.90-91 fixed:
Skill listing attachment —
skill_listing(~375 tokens) isn't persisted in session storage. On resume it regenerates at a different position, changing the block structure and breaking the cache prefix.joinTextAtSeam newline accumulation — text-block merging function appends
\nat boundaries during normalization. On replay, these accumulate, mutating the prefix each resume.My test results on v2.1.91 (npm, patched, through cc-relay proxy):
From resume 2 onward: 28-89 tokens created per turn instead of tens of thousands. Context carries over correctly — the 4th resume accurately summarized the entire session history.
The patch needed a small adaptation for v2.1.91 (the original regex for
joinTextAtSeamdidn't match due to a literal newline in the minified template string — used brace-counting to find exact function boundaries instead).Original gist: https://gist.github.com/simpolism/302621e661f462f3e78684d96bf307ba
For anyone using
claude -p --resumein harness setups, this is worth looking at. For interactive use where you start fresh sessions, the impact is minimal since v2.1.90-91 already fixed the main cache regression.simpolism commentedon Apr 3, 2026
@ArkNill thanks for posting it here -- I think there may still be a cache consistency issue around tool calls or file tracking across resumes. Hopefully can look into it shortly, having trouble reproducing the exact circumstances but got a pretty bad cache hit during a regular conversation on my patched version and will be keeping an eye out.
simpolism commentedon Apr 3, 2026
okay @ArkNill we found the remaining cache-breaking issues, they involved the TodoWrite and other injections happening before the cache prefix, invalidating the entire thing. I've updated the gist to a new version to deal with them as well. https://gist.github.com/simpolism/302621e661f462f3e78684d96bf307ba
full list of breaking attachments:
skill_listingtodo_remindersunified_taskschanged_filesdynamic_skillnested_memoryasync_hook_responsesdiagnosticslsp_diagnosticsjunaidtitan commentedon Apr 3, 2026
Prompt cache invalidation on resume happens because the session JSONL has mutated between sessions — Claude Code re-reads the full file and the cache prefix no longer matches.
Cozempic v1.4.1 reduces cache churn by keeping sessions lean. The guard daemon prunes bloat continuously so the session file changes less between turns. The SessionStart hook also runs a cleanup pass before the first API call.
pip install cozempic && cozempic initRun
cozempic current --diagnoseto check your cache hit rate — it now shows read vs creation tokens so you can see how efficiently caching is working.simpolism commentedon Apr 4, 2026
@blois why was this ticket closed? Still seeing the same poor caching behavior with --resume/--continue on 2.1.92.
github-actions commentedon Apr 11, 2026
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.