[BUG] 1h prompt cache intermittently invalidated every ~11-12 min in active sessions, full conversation rewritten at 2x input rate
Summary
In active long sessions, the conversation prompt cache (1h TTL entries) is intermittently invalidated at user-prompt boundaries, roughly every 11-12 minutes, even when the gap since the last API call is under a minute. Each event falls back to the ~19-22.4k system-prompt breakpoint and rewrites the entire conversation as ephemeral_1h cache creation (billed at 2x input rate). On Opus-class models with 150-250k context this costs $2-5 per event and burns the 5-hour subscription window very fast (one such event on a single-word "commit" prompt pushed me over the limit).
Environment
- Claude Code 2.1.205 (latest at time of filing), macOS (Darwin 25.5.0)
- Max subscription (OAuth),
service_tier: "standard", no API gateway - Observed on both
claude-fable-5(500k window) andclaude-opus-4-8; settingsmodel: opus[1m] - All cache writes are
ephemeral_1h_input_tokens(no 5m entries), so this is not the 5-minute TTL
Evidence (from transcript JSONL message.usage)
Session A (claude-fable-5, 2 user prompts total):
| time (UTC) | input | cache_creation (1h) | cache_read | note |
|---|---|---|---|---|
| 11:57:23 | 18419 | 13197 | 18886 | session start, first write |
| 11:57:37 | 37 | 34084 | 18979 | 9s later: did not read the 13k entry just written |
| 11:57:49 → 12:08:59 | 37-188 | 390-2168 per call | 53k → 154,608 | ~40 calls, caching healthy for 11 min |
| 12:09:40 | 185 | 133305 | 22436 | user prompt 30s after last call → full rewrite |
Session B (claude-opus-4-8 after a model switch, same machine, overlapping time):
| time (UTC) | cache_creation (1h) | cache_read | gap since previous call |
|---|---|---|---|
| 11:32:06 | 197136 | 18986 | model switch (expected rewrite) |
| 11:32:14 → 11:42:20 | small (163-4921) | 216k → 247k | healthy |
| 11:43:17 | 227615 | 22403 | 57 seconds |
| 11:43:26 → 11:51:31 | small | 250k → 273k | healthy |
| 11:54:49 | 252398 | 22403 | 3m18s |
| 11:57:57 | 505 | 283636 | healthy again |
Invalidation intervals: 11:32:06 → 11:43:17 (11m11s) → 11:54:49 (11m32s); Session A: 11:57:37 → 12:09:40 (12m03s). The periodicity is time-based, not content-based.
Ruled out
- Idle > TTL: gaps at failure were 30s-3m; entries are 1h TTL anyway.
- Local prompt mutation: UserPromptSubmit hooks (two, injecting
hook_additional_context) produce byte-identical structure/content on both invalidating and healthy prompts, and their content is appended to the new user message, after all cached history. - Compaction: zero
isCompactSummary/ microcompact markers in either transcript. - Config: intra-turn caching is flawless for 10+ minutes at a stretch (reads up to 283k with sub-1k creations), so caching is active and working between invalidation events.
Impact
Session B: 197k + 227k + 252k = ~677k tokens of 1h cache creation within 23 minutes on Opus 4.8 (~$6.80 at 2x input rate) for zero additional work. Session A: a one-word user prompt cost ~133k cache-creation tokens. Users on subscription hit the 5-hour limit from cache rewrites alone.
Repro / diagnostic
Any long session; watch for creations that jump to ~context size while reads collapse to the system-prefix size:
jq -r 'select(.type=="assistant" and .message.usage.cache_creation_input_tokens > 20000) |
[.timestamp, .message.usage.cache_creation_input_tokens, .message.usage.cache_read_input_tokens] | @tsv' \
~/.claude/projects/<project>/<session>.jsonl | sort -u
Possibly related: #51218, #40524, #34629. The ~11-12 minute periodicity and the 9-seconds-apart miss at session start suggest either server-side eviction of 1h entries or the client re-keying its cache breakpoints on a timer.