Session resume (--continue) invalidates entire prompt cache, causes massive rate limit consumption
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
- #34629 — Prompt cache regression in --print --resume since v2.1.69
- #40524 — Conversation history invalidated on subsequent turns
- #42136 — Aggressive context compaction on 1M context
- #38350 — Abnormal rate limit consumption
11 Comments
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.
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.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
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.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
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(~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.\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):
| Turn | Type | Create | Read | Ratio |
|------|------|--------|------|-------|
| 1 | Initial | 3,963 | 25,576 | 86.6% |
| 3 | Resume 1 | 4,799 | 29,539 | 86.0% |
| 4 | Resume 2 | 89 | 34,338 | 99.7% |
| 5 | Resume 3 | 28 | 34,427 | 99.9% |
| 7 | Resume 4 (30s idle) | 36 | 36,964 | 99.9% |
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.@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.
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:
| Attachment | What it does | Why safe to suppress |
|---|---|---|
|
skill_listing| Lists available slash commands | Interactive/IDE feature ||
todo_reminders| Periodic "use TodoWrite" nudges | Unnecessary in harness ||
unified_tasks| Task tracking state | Unnecessary in harness ||
changed_files| Diffs of previously-read files | Redundant (model sees tool results) ||
dynamic_skill| Scans skill directories | No dynamic skills in harness ||
nested_memory| One-shot memory triggers | Fires unpredictably ||
async_hook_responses| Intermittent hook feedback | No hooks in harness ||
diagnostics| LSP diagnostics | No LSP servers in CLI ||
lsp_diagnostics| LSP diagnostics | No LSP servers in CLI |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.@blois why was this ticket closed? Still seeing the same poor caching behavior with --resume/--continue on 2.1.92.
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.