[FEATURE] Every compaction re-writes ~97k unchanged tokens to cache because the summary is assembled first ( 700M tokens and $6.7k wasted per month on a 10-agent fleet)

Status Open
Maintainer reply None cached
Activity 0 comments · opened Sep 14, 2026

Preflight Checklist

  • [x] I have searched existing requests and this feature hasn't been requested yet
  • [x] This is a single feature request (not multiple features)

Problem Statement

A long session auto compacts several times in an afternoon, and every one of those throws away about 97,000 tokens of cache for content that did not change. Same CLAUDE.md, same skills, same tool schemas, same MCP instructions, re-sent and re-billed as a write on each compaction.

The cause looks like assembly order rather than anything expensive. After a compaction the rebuilt context puts the compaction summary first and the stable material behind it. Prompt caching matches on a prefix, so the summary is a guaranteed miss and everything behind it is a write. None of that material depends on the summary it is sitting behind.

The order I found, reading the session transcript at each compaction boundary:

system
compaction summary          <- unique every time, so the prefix breaks here
  file attachments            the files the session had read
  invoked skills
  deferred tool schemas
  agent listing
  MCP server instructions
  SessionStart hook output
next turn
  CLAUDE.md and its imports   re-attached on the next read into a directory it governs

Sizes below are a snapshot of one real session, counted with the count_tokens endpoint against Opus rather than estimated from character counts:

| what lands behind the summary | Claude tokens | changes between compactions? |
|---|---|---|
| CLAUDE.md and its imports | 76,668 | no |
| invoked skills | 9,410 | no |
| deferred tool schemas | 4,119 | no |
| agent listing | 3,873 | no |
| MCP server instructions | 2,130 | no |
| SessionStart hook output | 1,297 | no |
| files the session had read | 4,936 | yes, tracks what was read |
| total | 102,433 | |

About 97,500 of those tokens are the same on the next compaction as they were on the last one. They sit behind a block that is unique by construction, so they are re-written rather than read, every single time. A session that auto compacts several times in an afternoon pays that same 97,000 as a cache write on each one, for content that never changed.

CLAUDE.md is three quarters of it. This project's is a one-line pointer that imports a shared instructions file, which is how the chain reaches that size.

None of this content depends on the summary. It is identical either side of it.

Proposed Solution

Order the rebuilt context by volatility, most stable first, so the long prefix survives:

system
  CLAUDE.md and its imports
  invoked skills
  deferred tool schemas
  MCP server instructions
  SessionStart hook output
  agent listing
  file attachments
compaction summary          <- unique, so the break belongs at the end
next turn

Nothing is lost by moving the summary last. It is unique either way and costs a write either way. What changes is that the roughly 97,000 stable tokens in front of it can be served from cache.

The agent listing and the file attachments sit at the end of that block on purpose. Neither is guaranteed stable: the listing moves if agents are added mid-session, and the file set changes whenever the session reads something new. Putting them last means a change to either breaks the prefix there, rather than in front of the instruction files and the schemas.

If a full reorder is awkward, a smaller version still helps: keep the re-attached blocks in a fixed position and a fixed order across compactions within a session, so they form a stable region instead of moving with whatever the session happened to read.

If this is a change you would take from outside, I am happy to put up the PR. The script that produced the table above is small and I can include it, so the before and after are checkable rather than asserted.

Alternative Solutions

Shrinking what sits behind the summary works: load fewer skills and tools, read less into the session. That moves the cost onto the user rather than removing it, and none of those seem like the right thing to trade away to avoid paying twice for identical tokens.

Trimming CLAUDE.md helps linearly and leaves the ordering untouched. The instruction file is that size because the work needs it.

Priority

High - Significant impact on productivity

Feature Category

Performance and speed

Use Case Example

  1. Ten agents run through the day, each auto compacting about once an hour.
  2. Every compaction emits the summary first, then re-emits the skills list, tool schemas, agent listing and MCP instructions behind it, unchanged from last time.
  3. The next read into the project re-attaches CLAUDE.md and its imports behind the summary as well. Here that chain is about 77,000 tokens.
  4. About 97,500 tokens per compaction are billed as a cache write rather than a cache read, for content that never changed.

That is 240 compactions a day, 23.4M tokens a day, and 702M tokens a month of cache writes that could have been cache reads.

At published rates, using the 1-hour cache TTL a long-running coding session actually holds:

| model | cache write | cache read | wasted per month |
|---|---|---|---|
| Sonnet 5 | $4.00 / MTok | $0.20 / MTok | $2,668 |
| Opus 5 | $10.00 / MTok | $0.50 / MTok | $6,669 |

Put another way, on Sonnet those 702M tokens cost $2,808 a month to write and would cost $140 a month to read. A mixed fleet with orchestrators on Opus and workers on Sonnet lands between the two rows.

Assumptions are all in the open so anyone can redo it for their own setup: 10 concurrent agents, one compaction per agent per hour, 24 hours, 30 days, 97,500 unchanged tokens per compaction, published per-million rates. The unit that matters is the last one, and it is measured rather than assumed.

After the reorder, that prefix survives each compaction and is read from cache. Only the summary and the turns after it are new.

Additional Context

Related, and describing different mechanisms in the same area:

  • #94177 measures cache-break events: TTL expiry, microcompact, resume. This is about assembly order inside a single compaction, which is a separate cause.
  • #70459 describes a stale precompute keeping a large prefix verbatim, and that prefix being cache-created rather than cache-read. Adjacent, and a different fix.

Method, so the numbers can be checked. Session transcripts live under ~/.claude/projects/*/*.jsonl. Find the record carrying isCompactSummary, then read the attachment records that follow it. Each block is taken from the field that actually carries injected text: the content of each entry in skills, addedLines for the tool and agent deltas, addedBlocks for MCP, content.file.content for a file, and the content of a nested_memory record for an instruction file. Token counts come from the count_tokens endpoint against Opus, run on the extracted text.

Two double-counting traps, both of which inflate a figure if you sum a whole record instead of picking a field. A nested_memory record can carry the same file twice, once as content and again as rawContent. A hook_success record carries its output twice, once as content and once as stdout.

Caveat on scope: the sizes are one project's, and a project with a smaller instruction file pays proportionally less. The ordering is structural and independent of that. Whatever the stable material comes to, it currently lands behind a block that is unique by construction, so it is never the cache hit it could be.

View original on GitHub ↗