[BUG] Resume/continue cache invalidation
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
When using --resume to continue a session, the prompt cache is broken on every turn. Content that should be a cache hit is re-created instead, leading to massively increased session usage.
In particular, the cause is twofold:
Issue 1: Skill listing block migrates between messages on resume
A <system-reminder> block listing available skills (~1501 chars) is injected into the first user message on each turn. This block is not persisted in the session JSONL. On the initial turn it appears in messages[0]. On resume, the persisted messages are replayed without it, and a fresh copy is injected into the new user message instead.
This changes the block structure of messages[0], which invalidates the cache prefix for everything after it.
Observed in proxy dump — Turn 1 messages[0] (4 content blocks):
block[0]: 533 chars — companion system-reminder
block[1]: 1501 chars — skill listing system-reminder ← PRESENT HERE
block[2]: 7801 chars — project context system-reminder
block[3]: 4 chars — user text ("test"), cache_control: ephemeral 1h
Observed in proxy dump — Turn 2 messages[0] (3 content blocks):
block[0]: 534 chars — companion system-reminder (also 1 byte longer, see Issue 2)
block[1]: 7801 chars — project context system-reminder
block[2]: 4 chars — user text ("test"), NO cache_control
Turn 2 messages[2] (new user message, 2 content blocks):
block[0]: 1501 chars — skill listing system-reminder ← MOVED HERE
block[1]: 10 chars — user text ("test again"), cache_control: ephemeral 1h
The skill listing block moved from messages[0] (turn 1) to messages[2] (turn 2). Since the API caches by exact prefix match, this structural difference at the start of the messages array means the session-specific content can never be a cache hit on resume.
Issue 2: Extra newline appended to text blocks on re-normalization
When replayed messages are re-normalized on resume, an extra \n is appended to certain text blocks at merge boundaries. This is visible in the companion block:
Turn 1: 533 chars, ends with "</system-reminder>"
Turn 2: 534 chars, ends with "</system-reminder>\n"
This 1-byte difference independently invalidates the prefix cache, even if Issue 1 were fixed. The extra newline accumulates on each resume — a third resume would add another \n.
Expected behavior
Both issues are independently sufficient to break the cache. When both are corrected, resumed turns achieve ~100% cache hit:
turn hit% cached new_cache uncached total ctx% message
---- ---- ------- --------- -------- ------ ---- -------
1 68% 11271 5277 3 16551 2% test
2 100% 16548 45 3 16596 2% test again
3 100% 16593 44 3 16640 2% one more test
4 100% 16637 39 3 16679 2% test
HEALTHY: steady-state avg hit=100%, avg new cache=43
New cache creation drops from ~2850 to ~43 tokens/turn (just the new user message content).
What Should Happen?
claude --resume -p with a provided session, used within the cache time limit, should not invalidate prior session cache, nor introduce new tokens. In other words, --resume should be idempotent and --resume -p should be functionally identical to sending a new interactive turn, i.e. not generating unnecessary caching burden.
Error Messages/Logs
Steps to Reproduce
First, initialize a local HTTP reverse proxy intercepting API requests to api.anthropic.com, capturing full request payloads across a 4-turn --resume sequence.
# Turn 1: start session
claude -p --output-format stream-json <<< "test"
# → session_id: <id>
# Turns 2–4: resume
claude -p --output-format stream-json --resume <id> <<< "test again"
claude -p --output-format stream-json --resume <id> <<< "one more test"
claude -p --output-format stream-json --resume <id> <<< "test"
Observed cache behavior
turn hit% cached new_cache uncached total ctx% message
---- ---- ------- --------- -------- ------ ---- -------
1 0% 0 21220 3 21223 2% test
2 87% 18416 2828 3 21247 2% test again
3 87% 18416 2847 3 21266 2% one more test
4 87% 18416 2866 3 21285 2% test
UNHEALTHY: steady-state avg hit=87%, avg new cache=2847
Turns 2–4 should show ~100% cache hit with <100 tokens of new cache creation (just the new user message). Instead, ~2850 tokens are re-created on every resumed turn.
Claude Model
Opus
Is this a regression?
Yes, this worked in a previous version
Last Working Version
2.1.71
Claude Code Version
2.1.92
Platform
Anthropic API
Operating System
Ubuntu/Debian Linux
Terminal/Shell
Non-interactive/CI environment
Additional Information
#42338 contained extensive documentation of these and other issues, including minified source patches, but was closed prior to 2.1.92 launching.
This issue has 14 comments on GitHub. Read the full discussion on GitHub ↗