[BUG] Image eviction in long sessions mutates the conversation prefix, forcing a full context re-cache on every subsequent image read

Status Open
Reported on v2.1.220
Maintainer reply None cached
Activity 1 comment · opened Aug 30, 2026

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?

Once a session accumulates roughly 40 page-sized images, Claude Code appears to drop the earliest images from the conversation it sends. That mutates the prefix, so every cache breakpoint after the system/tools block is invalidated and the entire context — 600–740k tokens in my case — is re-written to the cache. From then on it happens on every turn that reads a normally-sized image.

There is no error and no warning. The only visible symptom is that usage limits are consumed several times faster than expected.

Before the threshold, per image read:

cache_read = 658,681 cache_creation = 3,439 <- image costs ~3.4k

After the threshold, per image read:

cache_read = 26,314 cache_creation = 590,100 <- entire context re-written

cache_read collapses to a value constant for the lifetime of the session — 26,314 in one session, 26,317 in another — consistent with the only surviving cache breakpoint being the one at the end of the system prompt and tool definitions.

Evidence that the prefix is mutated. Define promptTotal = input_tokens + cache_read_input_tokens + cache_creation_input_tokens, the size of what was actually sent. Absent mutation it can only grow. At the first collapse in two independent sessions it drops:

| session | promptTotal before -> after | delta |
| --- | --- | --- |
| A | 623,650 -> 570,168 | -53,482 |
| B | 670,690 -> 616,416 | -54,274 |

~54k tokens is about 16 page images at the measured 3,361 tokens each — a one-time bulk trim. Every collapse after that is one-in-one-out: a new ~3.3k image is added and the total moves by only +113 or +149.

700,887 -> 701,000 (+113) cache_read=26,317 cache_creation=674,681
703,923 -> 704,036 (+113) cache_read=26,317 cache_creation=677,717
709,154 -> 709,395 (+241) cache_read=26,317 cache_creation=683,076

So an old image is dropped to make room for each new one, and the full context is re-cached each time.

Which images trigger it. In the stretch after the first collapse, every full-page render collapsed the cache and every small crop did not:

p188.png 3.3k tokens FULL RE-CACHE
p189.png 3.3k tokens FULL RE-CACHE
p189crop.png 0.4k tokens no collapse
p189d.png 0.3k tokens no collapse
p190.png 3.3k tokens FULL RE-CACHE
...
p200.png 3.3k tokens FULL RE-CACHE
p200crop.png 0.4k tokens no collapse
p201.png 3.3k tokens FULL RE-CACHE

20 of 20 full pages collapsed; 0 of 3 crops did. Across four sessions the first collapse landed at image #41, #45 and #49, and a fourth session that read only 24 images never triggered it despite reaching 688k of context.

Since the shrink appears in the billed prompt size, and the API counts what it receives, the trimming appears to happen client-side before the request leaves.

What Should Happen?

An append-only conversation prefix, so the prompt cache keeps working for the life of the session. Any of these would help, roughly in order of preference:

  1. Do not mutate the prefix. Refusing to add a new image, or degrading it, preserves the cache; silently rewriting history does not.
  1. If eviction is necessary, amortize it. Evicting one image per turn is the pathological case for prompt caching — maximum invalidation frequency for minimum reclaimed space. Trimming a large batch once, and not again for many turns, would cost a single re-cache instead of one per turn.
  1. Surface it. A one-line notice that images were dropped, or a cost warning, would have saved hours of usage here. As of 2.1.220 the only way to discover it is to parse the transcripts afterwards. (2.1.251 adding prompt-cache stats to /cost should make it visible after the fact, but it would still be silent while running.)

Error Messages/Logs

There are none, and that is a significant part of the problem. Checked directly across all four transcripts:

    "could not be processed"   0 occurrences
    invalid_request_error      0 occurrences
    apiErrorStatus: 400        0 occurrences

Image dimensions were 1323x1871, under the 2000px many-image limit, and API responses reported "context_management": null, so server-side context editing was not active. Nothing was logged when the eviction began.

The closest thing to a log is the usage accounting, which is the trace quoted in "What's Wrong?" above.

Steps to Reproduce

  1. Start a long-running session with a large context budget (mine were 1M-context Opus sessions running non-interactively).
  1. In a loop, render one PDF page to PNG at roughly 1300x1900 px (~3.3k tokens) and read it, doing a small amount of text work per page:

pdftoppm -r 160 -f $N -l $N -png input.pdf tmp/p$N
# then Read tmp/p$N-0$N.png and verify the page

  1. Let it run for 50+ pages without restarting the session.
  1. Watch cache_creation_input_tokens per turn in the session transcript

(~/.claude/projects/<escaped-cwd>/<session>.jsonl).

Expected: it stays in the low thousands per image.
Actual: somewhere past ~40 images it jumps to roughly the full context size and stays there for every subsequent full-size image.

Collapses in my runs were 50–65 seconds apart, i.e. once per page, which rules out TTL expiry. The one genuine TTL event in my data looks entirely different — cache_read=0, cache_creation=739,830, once, after the session sat idle overnight.

To check an existing transcript rather than reproducing: a collapse is any turn where cache_read_input_tokens drops to a session-constant value while cache_creation_input_tokens jumps to roughly the context size.

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.220 - Not re-tested on 2.1.251. I checked the changelog for 2.1.221–2.1.251 and found nothing addressing image handling or context eviction; the nearest entries are promptCacheTtl settings (2.1.243), a fix for an hourly prompt-cache miss in long sessions (2.1.247), and prompt-cache stats added to /cost (2.1.251).

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

Non-interactive/CI environment

Additional Information

Impact. Same session, same loop, same work, split at the moment eviction engaged. Weighted = input + cache_creation(1h)*2 + cache_read*0.1 + output*5; all cache writes were ephemeral_1h:

| | duration | pages | cache write | burn rate |
| --- | --- | --- | --- | --- |
| before | 82 min | ~40 | 0.67M | 12.1M/hour |
| after | 31 min | ~19 | 13.50M | 68.5M/hour |

5.7x the cost per page for identical work. In one session this consumed an entire 5-hour allowance in 31 minutes; in another, 26 re-caches totalling 16.3M cache-write tokens.

Related issues.

  • #90675 — "entire 5-hour usage limit exhausted ~23 minutes after reset". That reporter had a concurrent session reviewing image files but no cache metrics; this may be the same root cause.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗