[BUG] Prompt Cache: Full Conversation Prefix Re-written 3 times Seconds Apart (Multiple Sessions & Models)

Status Open
Maintainer reply None cached
Activity 2 comments · opened Jul 30, 2026

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 UserPromptSubmit hook (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 and
cache_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:

  1. At 13:31, one minute after the reset, /usage still showed 100%. The displayed window

boundary and the billing window boundary may not be on the same clock.

  1. 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

  1. 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.

  1. 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.

  1. Surface cache_creation_input_tokens spikes in /usage or 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.

  1. 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.

  1. 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.

  1. 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 same
usage object. My primary session logged 471 assistant records for 220 real requests. Dedupe by
requestId or you will over-count by ~2×.

Detection predicate used: cache_creation_input_tokens > 100_000 AND cache_read_input_tokens <
0.3 * cache_creation_input_tokens
, deduped by requestId, 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.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗