[BUG] Prompt Cache: Full Conversation Prefix Re-written 3 times Seconds Apart (Multiple Sessions & Models)
Prompt cache: full conversation prefix re-written on turns seconds apart — 31% of 3 weeks of cache-write spend, recurring across 4 model families
Summary
Claude Code intermittently re-writes the entire conversation prefix to the prompt cache on a
turn that should have been a near-total cache hit. cache_read_input_tokens collapses to just
the static system-prompt + tools segment (~20–24k) while cache_creation_input_tokens jumps to
the full context size (observed up to 899,075 tokens in a single request).
I scanned 3 weeks of my own local transcripts: 732 sessions, 13,688 API requests, 83,666,423
total cache_creation_input_tokens.
- 85 full-prefix rewrite events across 27 sessions and 5 projects
- Those 85 events = 26,211,061 cache-write tokens = 31.3% of ALL cache-write in 3 weeks
- 25 of them happened with less than 300s of idle time before the request — inside even the
shortest cache TTL. That subset alone is 7,454,315 cache-write tokens ≈ 9.3M
input-token-equivalents (writes bill at 1.25×)
- 11 events with under 60s idle; the tightest is 11 seconds
- 9 events read back 0 tokens — even the static system prefix missed
- Occurs on **
claude-opus-4-8(30),claude-opus-5(42),claude-fable-5(11),
claude-sonnet-5 (2)** — not model-specific
- Also occurs in subagent and workflow transcripts, not just main sessions
The user-visible symptom is a session budget vanishing with nothing to show for it. In the
incident that prompted this report, 743,839 cache-write tokens were billed in 70 seconds while
total output for the window was 17,334 tokens — roughly 30% of a 5-hour session limit consumed
by cache writes, not generation.
I am reporting two things: (A) the rewrite-on-consecutive-turns behaviour, and (B) a
session-usage accounting question at the limit-window reset boundary.
Environment
- Claude Code CLI, macOS (Darwin 25.5.0), zsh
- Models affected:
claude-opus-4-8,claude-opus-5,claude-opus-5[1m],claude-fable-5,
claude-sonnet-5
- Contexts involved: 100k–900k tokens
- Hooks active in the affected sessions: a
UserPromptSubmithook (per-prompt context
injection), a PreCompact hook, SessionStart hook output
- Heavy Edit/Bash tool loops; some sessions include image attachments and subagent/workflow runs
- The primary incident session declared: *"This session's requests use a 1-hour Anthropic
prompt-cache TTL", with the caveat "If the session enters usage overage, later requests drop
to the 5-minute TTL"*. The account was in usage overage during that incident.
Times below are Europe/Berlin (CEST, UTC+2).
(A) Full-prefix rewrite on a turn seconds after the previous one
Clearest single repro — two rewrites 41 seconds apart
Same session, claude-fable-5, consecutive requests:
13:35:14 cacheWrite=370497 cacheRead=24119 req_011CdYAso2C4HxMbkLM95ZgW
13:35:55 cacheWrite=373342 cacheRead=24119 req_011CdYAxUfH9AjBEotAUfKxw <- 41s later, missed again
13:36:24 cacheWrite= 1804 cacheRead=397461 req_011CdYB1Fycqjboh1pgUStaQ <- recovered
The 370,497 tokens written at 13:35:14 were billed and never read by any request. The turn
41 seconds later re-wrote the same prefix from scratch. The turn after that hit correctly,
reading 397,461. No TTL explains a miss at 41s, and the recovery one turn later shows the data
was cacheable the whole time.
4 of the 85 events are immediately followed by another full rewrite like this.
Cross-session evidence: rewrites with <300s idle before them
Every one of these is inside even the 5-minute overage TTL, so eviction cannot be the cause:
date/time (Berlin) model idle cacheWrite cacheRead
2026-07-28 10:20 claude-opus-5 82s 899075 20601
2026-07-23 17:22 claude-opus-4-8 245s 411677 20532
2026-07-27 17:47 claude-opus-5 70s 398768 20601
2026-07-24 18:49 claude-fable-5 17s 389753 24305
2026-07-28 11:17 claude-opus-5 50s 378091 20601
2026-07-30 13:35 claude-fable-5 41s 373342 24119
2026-07-24 18:39 claude-fable-5 14s 359981 24305
2026-07-24 21:05 claude-opus-5 208s 346415 24105
2026-07-30 12:37 claude-fable-5 99s 340873 24119
2026-07-27 16:49 claude-opus-5 82s 318111 20601
2026-07-25 07:48 claude-opus-5 48s 300892 21997
2026-07-30 12:13 claude-fable-5 38s 299100 24119
2026-07-16 17:46 claude-opus-4-8 152s 253730 23401
2026-07-27 13:29 claude-sonnet-5 271s 247018 28923
2026-07-28 17:31 claude-opus-5 179s 232208 24109
2026-07-28 17:25 claude-opus-5 41s 219377 24109
2026-07-24 17:42 claude-fable-5 52s 211015 24305
2026-07-17 18:14 claude-opus-4-8 11s 208908 56930
2026-07-28 14:34 claude-opus-5 104s 203921 24109
2026-07-30 11:37 claude-fable-5 158s 199215 24119
2026-07-13 11:24 claude-opus-4-8 82s 187750 21749
2026-07-13 12:01 claude-opus-4-8 23s 185656 0 (workflow subagent)
2026-07-16 17:58 claude-opus-4-8 104s 181065 23722
2026-07-28 17:07 claude-opus-5 49s 120624 0 (subagent)
(One row appears twice in my scan because a resumed session logs the same requestId in two
transcript files — 24 distinct events, 25 records.)
The escalation pattern
Within a single session, each successive rewrite costs more, because the prefix being re-written
keeps growing. One session, 558 requests:
2026-07-27 16:30 cacheWrite=240995 cacheRead=20601
2026-07-27 16:49 cacheWrite=318111 cacheRead=20601
2026-07-27 17:47 cacheWrite=398768 cacheRead=20601
2026-07-27 21:15 cacheWrite=562592 cacheRead=20601
2026-07-27 22:13 cacheWrite=677381 cacheRead=24108
2026-07-28 08:00 cacheWrite=740887 cacheRead=20601
2026-07-28 09:54 cacheWrite=784944 cacheRead=20601
2026-07-28 10:20 cacheWrite=899075 cacheRead=20601 <- 82s idle before this one
8 events = 4,622,753 tokens = 83% of that session's entire cache-write spend. The last one
re-wrote 899k tokens after 82 seconds of idle.
cache_read_input_tokens is pinned at exactly the same value (20601 / 24119 / 24305 / 21997,
depending on session) across every rewrite in a session, over many hours. Only the first cache
breakpoint — static system prompt + tool schemas — ever hits on these turns. The
conversation-body breakpoint is written and never read.
Expected vs actual
Expected: a request issued seconds after the previous one, same model, same session, prefix
unchanged apart from appended turns, reports cache_read_input_tokens ≈ N andcache_creation_input_tokens ≈ delta.
Actual: cache_read_input_tokens drops to the static-prefix size and the full body is
re-written. Sometimes twice in a row before it recovers on its own.
Idle-gap distribution for all 85 events
<60s 11 (13%)
60-300s 14 (17%)
300-3600s 21 (25%)
>3600s 37 (45%)
median 1102s
The 37 events after >1h idle are legitimate expiry and I am not claiming those are bugs — but
see the visibility request below, because a legitimate 900k-token rewrite is still a large
silent charge. The 25 events under 300s are the defect.
(B) Session-usage accounting at the limit-window reset
Timeline for the incident that prompted this, on 2026-07-30:
| time (Berlin) | event |
|---|---|
| ~13:23 | hit 100% of the 5-hour session limit; continued on usage credits (overage) |
| ~13:29 | Fable 5 (effort: high) actively working a task |
| 13:30 | session limit window resets |
| 13:31 | /usage still reported 100% session usage, one minute after the reset |
| ~13:33 | /usage reported ~30% of the new window already consumed |
| 13:35:14 | full prefix rewrite, 370,497 tokens |
| 13:35:55 | full prefix rewrite, 373,342 tokens |
| 13:36:31 | user interrupts all work |
| 13:36:45 | /compact |
| 13:40:45 | switch to Opus 5 to investigate |
Transcript-verified: the interrupt, compact, and model switch match to the second.
Two observations:
- At 13:31, one minute after the reset,
/usagestill showed 100%. The displayed window
boundary and the billing window boundary may not be on the same clock.
- The ~13:33 reading of ~30% consumed predates any billed request in the new window. The
transcript shows no API request at all between 13:27:15 and 13:35:14. Either pre-reset
spend was attributed to the post-reset window, or my ~13:33 recollection is off by ~2 minutes
and the reading actually came after 13:35:55 — in which case (A) fully accounts for it and (B)
is not a bug. I cannot settle this from the transcript; flagging it in case the reset-boundary
accounting is worth a look on your side.
Requests
- Fix the sub-300s rewrites. A request seconds after a successful cache write should not
re-write the whole prefix. The self-recovery one turn later suggests a write-visibility or
breakpoint-identity problem rather than expiry.
- Verify the declared TTL is actually applied to the conversation-body breakpoint, not only
the system prefix. Sessions advertising a 1-hour TTL show body misses after 8 minutes.
- Surface
cache_creation_input_tokensspikes in/usageor the status line. Today a
370k–899k prefix rewrite is completely invisible; it looks identical to normal work while
consuming a third of a session budget. This is the single change that would have saved me from
burning two session windows without knowing why.
- Check cache-breakpoint stability when hooks inject content mid-conversation. In the
13:35 incident, hook output landed at 13:35:31 and 13:35:41 — between the two failing
requests. A UserPromptSubmit hook appending per-prompt text, or file-state
system-reminders, could be shifting content behind a breakpoint and invalidating everything
after it.
- Reconsider shortening the cache TTL on overage (or warn clearly). Overage is exactly when
a miss is most expensive, and the shortened TTL makes misses more frequent — a feedback loop
that accelerates spend precisely when the user is already paying premium.
- Consider crediting back demonstrable sub-TTL rewrites. 9.3M input-token-equivalents over
three weeks from the <300s subset alone is not a rounding error.
Note for anyone reproducing this analysis
Transcripts emit one assistant record per content block, each carrying a copy of the sameusage object. My primary session logged 471 assistant records for 220 real requests. Dedupe byrequestId or you will over-count by ~2×.
Detection predicate used: cache_creation_input_tokens > 100_000 AND cache_read_input_tokens <, deduped by
0.3 * cache_creation_input_tokensrequestId, with idle gap measured to the
previous request in the same transcript.
Local transcripts with full per-record usage (468 MB across 732 sessions) are retained and can
be supplied on request, including the two requestIds named above.
Showing cached comments. Read the full discussion on GitHub ↗
3 Comments
Confirming this on Windows with a larger sample — same pattern, higher share of cache-write spend.
Environment: Claude Code v2.1.220, Windows, Max 20x. Models:
claude-opus-5,claude-opus-4-8.I ran your detection over my own transcripts (10 days, 2026-07-20 onward), deduplicated by
message.id:cache_creation_input_tokensClearest short-idle examples:
| Time (local) | Model | cache_write | cache_read | idle |
|---|---|---|---|---|
| 07-30 09:21:28 | claude-opus-5 | 532,933 | 29,796 | 21s |
| 07-23 13:23:54 | claude-opus-4-8 | 541,154 | 29,914 | 28s |
| 07-24 16:47:18 | claude-opus-4-8 | 598,352 | 38,571 | 35s |
| 07-24 06:25:05 | claude-opus-4-8 | 806,011 | 29,930 | 49s |
| 07-30 09:14:47 | claude-opus-5 | 519,030 | 25,690 | 177s |
| 07-28 00:15:41 | claude-opus-5 | 529,866 | 28,748 | 199s |
cache_readpins at 25k–38k across these — matching your observation that only the static system-prompt + tools breakpoint ever hits, while the conversation body is written and never read. Split acrossclaude-opus-4-8andclaude-opus-5alike, consistent with your finding that this is not model-specific.Two caveats on my numbers:
Separately I had been tracking this from the usage-meter side in #81234 and #82113 — my workload is ~90% cache-read by token count and the weekly meter moves far faster than output volume would suggest. This report gives a concrete mechanism that fits that far better than the metering theories I was working from.
Angel here, founder of Runa. Your 85-event analysis puts a hard number on the waste: 31.3% of all cache writes, with 25 misses inside even the shortest TTL. The Windows replication at 55.7% makes this hard to dismiss as one environment.
I'm building https://runacode.io around the cost and visibility side of this problem: run Claude Code on a cloud machine, and Run Inspector shows where the tokens went and what was saved. We've measured about 46% lower token cost on real runs. New users get $50 of machine usage (you bring your own model API key).
If you're open to trying it on one real task from your repo, I'll pair with you for 30 minutes and compare the run in Inspector. If it doesn't cut tokens without hurting the work, we call it a miss. Worth a try?
Replication with the same method on a different machine, plus a version-boundary observation that may help triage.
641 local sessions, 133,824 calls, Claude Code 2.1.156 → 2.1.272, Windows, Claude subscription (1h TTL), first-party API, usage deduplicated by
message.id. A "break" = a call whosecache_creationexceeds 20% of the previous context and 20k tokens.| cause | events | tokens written | share of all cache writes |
|---|---:|---:|---:|
| TTL (gap > 60 min) | 745 | 306M | 26% |
| resume / model switch | 161 | 66M | 6% |
| full re-write with no gap (
cache_read= shared prefix only, context did not shrink) | 1,336 | 570M | 48% || all breaks | 2,258 | 949M | 80% |
Your 31% of write spend in 85 events is the third row; here it is 48% in 1,336 events, median re-write 431k tokens. Over all 132,016 no-gap boundaries (same model, no shrink) it happens 0.9% of the time: 1.4% after a user turn vs 0.2% after a tool call — the new user turn is where the client re-renders the prefix.
What the transcript holds between the two calls, and the re-write rate when it is present: effort change 30.8% (169 events), tool-list delta 10.6%, MCP reconnect 10.2%, skills delta 9.5%,
UserPromptSubmithookadditionalContext8.3% (5,877), baseline 0.9%. Those explain ~160 events / 65M tokens. The rest is concentrated by client version, not by anything the user did:| Claude Code version | boundaries | re-wrote | rate |
|---|---:|---:|---:|
| 2.1.215 | 1,288 | 419 | 32.5% |
| 2.1.181 / 2.1.202 / 2.1.209 | 15,763 | 283 | 1.8% |
| 2.1.219 / 2.1.221 / 2.1.205 | 18,335 | 156 | 0.9% |
| 2.1.229 / 2.1.222 | 19,060 | 94 | 0.5% |
| 2.1.237 and later | 60,000+ | 71 | 0.1–0.2% |
One 2.1.215 session (Sonnet 5, 992 calls, 16 h) holds 417 of them: from ~566k context to the end nearly every call re-wrote ~520k with only the 48k shared prefix cached — 216M tokens in one session, and the only transcript line between the last cached call and the first re-written one is a
custom-titleupdate. Your report (2026-07-30, Opus 4.8 / Opus 5 / Fable 5) predates 2.1.237, i.e. it sits in the same client range. Whatever it was, it is mostly gone after 2.1.237: 36 unexplained events in 60k+ boundaries, 11.9M tokens. The rate still rises with context size (0.4% under 200k → 1.9% at 500–800k).Scripts (
tools/breaks.py,tools/rewrites.py,tools/ledger.py) and the write-up: https://github.com/edubraqd/claude-context-forensics,report/findings.md§4.