[BUG] Warm prompt cache fully re-written (cache_read collapses to system+tools floor) seconds after ToolSearch / Skill / tool_result on v2.1.258 — 4 events with request IDs; plus pre-2.1.257 screenshot size-cap data

Status Open
Reported on v2.1.250
Maintainer reply None cached
Activity 6 comments · opened Sep 2, 2026

Environment

  • Claude Code v2.1.258 (macOS 25.4, Apple Silicon), Claude Max 20x subscription (OAuth login, no API key / gateway)
  • Model: claude-fable-5-1 (events below); the same signature was observed earlier on claude-opus-4-8 and claude-sonnet-5
  • A second machine (Windows 11, same account, Claude Code 2.1.250–2.1.252) shows the same signature independently

Summary

Mid-session, with a warm, established prompt cache (≥100k tokens read on the previous call, same model, ≤65 s earlier), a single call re-writes the whole context: cache_read_input_tokens collapses to the system+tools floor (33,376–33,405 on Fable 5.1; 27,182 on Opus 4.8) and cache_creation_input_tokens ≈ the entire conversation. The very next call reads the re-written cache normally again. Nothing in the conversation changed except an appended tool_result — so the invalidation must come from the client re-rendering the request prefix (tools / system / first message).

This looks like the class already described in #63930 (deferred-tool materialisation collapsing cache_read to the floor) and #81967 (tools array mutation invalidates everything after it). It is still present on 2.1.258.

Events on v2.1.258 (2026-09-02, Fable 5.1) — request IDs for cache diagnostics

Each row: the last GOOD call → the call that re-wrote the cache. "Trigger" = tool(s) whose result was appended between them.

| # | eviction (UTC) | request IDs (good → eviction) | gap | cache_read before | cache_read at eviction | cache_creation at eviction | trigger (tool result appended in between) | screenshots in history |
|---|---|---|---|---|---|---|---|---|
| B1 | 05:13:17Z | req_011Cee3DV4pAp9ca9hVD9Msd → req_011Cee3GufVmAn48jVFADzEU | 44 s | 329,438 | 33,376 | 337,258 | Bash, Bash | 1 |
| B2 | 05:43:01Z | req_011Cee5a8NwAWLH7JUT4eBhX → req_011Cee5aUcr9mWFkjzd2TNFJ | 5 s | 133,834 | 33,403 | 111,403 | ToolSearch | 1 |
| B3 | 06:29:21Z | req_011Cee96dKQrc63h5MnPUdWG → req_011Cee97HeG6CEvsuUDW4TFb | 7 s | 179,492 | 33,405 | 154,475 | ToolSearch | 0 |
| B4 | 07:15:09Z | req_011CeeCWoCVjihFg4ieDFar1 → req_011CeeCYvMfeab85CV99UcuQ | 65 s | 752,216 | 33,376 | 726,403 | playwright:browser_run_code_unsafe, chrome:computer | 53 |

Notes per event:

  • B2 / B3: the preceding call was ToolSearch (deferred tool schemas materialised). 5–7 s later the next request misses everything after the tools block. This is exactly the #63930 "Mode A".
  • B1: the preceding call was a Skill invocation earlier in the same minute (skill content injected), then Bash; the request after it missed everything.
  • B4: the biggest one (726k re-written, ≈$9 at list on Fable pricing). Between the good call and the eviction: a Playwright tool result (Connection closed) and a <system-reminder> block injected into the following user turn. The cache floor is identical to B1 (same session), i.e. the miss starts right after the tools block.

All four: same model, cache warm (5–65 s), no /compact, no /model switch, no resume, request well under the size cap (0–53 screenshots).

Detection method (so the numbers are not inflated)

Local transcripts (~/.claude/projects/**/*.jsonl) parsed one API call per message.id (the transcript writes one line per content block, all repeating the same usage — counting lines over-counts ~2.2×). A call is classed as an eviction only if: cache established (≥100k read on the previous call), same model, ≤300 s since previous call, and cache_read collapses below 50% of the established size while cache_creation ≥ 50% of it. Session-start priming, model switches and >5-min TTL re-caches are excluded. Per-event dossiers (all fields above + everything appended between the calls) are available on request.

Related, pre-2.1.257 class (for confirmation that the fix covers it)

On 2.1.250–2.1.252 the same account had 110 evictions over 31 Aug–1 Sep on two machines, all in long screenshot-heavy Claude-in-Chrome sessions. Measured on the raw transcripts: once the cumulative base64 image payload in history reached ~28–40 MB (~21–30 MB binary) — machine A from screenshot #298, machine B from #276 — every subsequent screenshot turn became a full re-write (cache_read = 27,182 floor, cache_creation 600k–950k, ≈$11–17/call at Opus list). Smoking-gun pair, same conversation, 15 s apart, warm cache: req_011CecStEr48YXCWRaUFXc6v (cache_read 833,928 / cache_creation 2,136) → req_011CecStv4jEeDLZwgtLr2vh (cache_read 27,182 / cache_creation 696,858), with only one screenshot tool_result in between. This matches the 2.1.257 changelog entry "Fixed prompt-cache misses on every turn in long screenshot-heavy sessions once images exceeded the per-request size cap." — could you confirm that fix targets exactly this (image stripping mutating early history), and whether a per-image/count threshold is involved? Since 2.1.257 we have not reproduced this class; the 4 events above are the remaining (tool-set) class.

Expected

Materialising deferred tools / injecting skill or system-reminder content mid-session should not invalidate the cached prefix for the whole conversation (e.g. append new tool definitions after the cached breakpoint, or keep the tools block byte-stable and place the cache breakpoint before mutable content).

Impact

On a subscription plan this burns the session/weekly quota ~10× faster per affected call (each event = re-caching the entire context at cache-write rates). Over 5 days: 114 real evictions ≈ $1.6k at list-equivalent, ~2/3 of which on the pre-2.1.257 screenshot class.

View original on GitHub ↗

3 Comments

github-actions[bot] · 1 month ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/87234
  2. https://github.com/anthropics/claude-code/issues/88142
  3. https://github.com/anthropics/claude-code/issues/84847

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

ArshansGithub · 1 month ago

The read-collapse-to-floor after a tool_result on 2.1.258 matches #83913; 2.1.259 fixed it in my capture. Your pre-2.1.257 screenshot data is a separate, still-live residual: a pasted image is re-rendered as a file pointer on the next request → one rewrite (https://github.com/anthropics/claude-code/issues/91705). Workaround that sidesteps it: give the model a path to Read instead of pasting, https://github.com/ArshansGithub/shotpath.

ArshansGithub · 1 month ago

Correction to my earlier comment here: the pasted-image residual (#91705) is withdrawn and closed. The image block is byte-stable across requests; the collapse I attributed to it was a persisting cd rewriting the system prompt (#91706). The 2.1.259 fix for the tool_result collapse stands.

Showing cached comments. Read the full discussion on GitHub ↗