Research
What /compact Actually Costs: 126,464 Tokens Your Transcript Never Records
· 8 min read
On this page
Search for what /compact costs and you land in the same Reddit thread everyone else does, where two confident answers sit next to each other. One says the session is already in the KV cache server-side, so you only pay for the compaction itself. The other says compaction reads the entire session back, so every morning you are paying for one large uncached read. A third reply suggests that starting a new session would probably have been cheaper.
Nobody in that thread has numbers. The official docs do not have them either — the costs page explains that token costs scale with context size, and the platform docs describe compaction as a feature with an “additional compaction cost,” which is true and unhelpful.
So I measured it.
The rig
Claude Code 2.1.246, --model sonnet (claude-sonnet-5), default effort. The fixture is a generated TypeScript codebase: 30 files, about 256KB, each file exporting 24 near-identical functions. Nothing about it is interesting, which is the point — it is context ballast with a known size.
Each run is a headless session created with a fixed id:
claude -p --session-id "$SID" --model sonnet \
"Read all 30 files in src/ with the Read tool, one call per file. \
Then print one line per file: filename, number of exported functions."
and then compacted by resuming that same session:
claude -r "$SID" -p "/compact"
The measurement comes from Claude Code’s own OpenTelemetry metrics, dumped to the console:
export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_METRICS_EXPORTER=console
export OTEL_METRIC_EXPORT_INTERVAL=2000
That gives you claude_code.token.usage broken out by type (input / cacheCreation / cacheRead / output) and, usefully, by a query_source attribute. Both are cumulative counters, so I take the final value per (model, query_source, type).
I started somewhere else, though — parsing ~/.claude/projects/<project>/<session>.jsonl, which is where every transcript-based cost tool reads from. That turned out to be the first finding.
What came back
| Run | What it is | input (uncached) | cacheWrite | cacheRead | output | total | cost |
|---|---|---|---|---|---|---|---|
| A1 | read 30 files | 6 | 152,125 | 102,429 | 4,171 | 258,731 | $0.671 |
| A2 | /compact, 46s later | 126,464 | 15,704 | 30,287 | 4,977 | 177,432 | $0.348 |
| A2b | same read, repeat | 6 | 153,054 | 102,513 | 4,243 | 259,816 | $0.675 |
| A2b | /compact, 43s later | 126,464 | 16,169 | 30,287 | 4,490 | 177,410 | $0.344 |
| B1 | same read | 6 | 140,637 | 113,997 | 4,226 | 258,866 | $0.628 |
| B2 | /compact after 7 min idle | 126,464 | 15,746 | 30,287 | 7,138 | 179,635 | $0.370 |
Three things fall out of that table, and none of them is the thing the tips articles tell you.
1. The cost is not in your transcript
The session JSONL records the compaction. There is a user entry with isCompactSummary: true holding the full summary — 4,655 characters of it in run A. What there is not is a usage block for the request that produced it. I walked every line of the file and deduplicated by requestId: four requests, all of them ordinary conversation turns, none of them the compaction.
In telemetry the same work shows up plainly, tagged query_source: "auxiliary" instead of "main". It is a real API call against a real model. It just does not land in the transcript.
If you track your spend with anything that reads those JSONL files, your compactions are invisible to it. In this experiment that is a third of the total bill missing.
2. /compact does not use the cache, at all
Look at the input column for the three compaction runs. 126,464 tokens, three times, to the token. The cacheRead column is 30,287 all three times too — that is the system prompt and tool definitions, not the conversation.
Run A2 compacted 46 seconds after the session finished, with the cache as warm as it gets. Run B2 compacted after seven minutes of idling, past the five-minute prompt cache TTL. The input is identical. Not close — identical.
This kills the most upvoted piece of advice on the subject. “Compact while the cache still holds the context, or you’ll pay full price” describes a mechanism that is not running. The compaction request is assembled fresh, as raw input, every time. There is no warm path to catch.
What does differ across the three runs is the summary length (4,490 / 4,977 / 7,138 output tokens), which is the only reason the costs are not identical either.
3. Compaction is roughly half of what the session cost to build
Loading those 30 files cost $0.671. Compacting the result cost $0.348. That ratio is the number worth carrying around: one /compact costs about half of what it cost to fill the window in the first place, because it re-reads the window as uncached input while the original fill got cache discounts on most of it.
It also means compaction cost scales with how full your context is when you trigger it, not with how much gets thrown away. Compacting a nearly-full window is the expensive case, and a nearly-full window is exactly when you reach for it.
So, /compact or /clear?
This is the comparison people actually want, so I ran it four ways. After compacting, I asked a small follow-up that requires reading two files, and I asked the identical question in a fresh session — each of those under both a warm cache and a seven-minute idle.
| tokens | cost | ||
|---|---|---|---|
| continue in the compacted session | warm | 67,523 | $0.156 |
| continue in the compacted session | cold | 137,757 | $0.179 |
| ask in a fresh session | warm | 171,176 | $0.087 |
| ask in a fresh session | cold | 170,890 | $0.089 |
The fresh session costs about half as much either way, and it does so while using two to three times more tokens. That is not a typo, and it is the most counterintuitive result here.
The reason is in the breakdown. The compacted session’s turn was 38,711 tokens of cacheWrite against 98,603 of cacheRead: after compaction the whole context is new, so nothing can be read from cache and everything has to be written to it — at 1.25x the base input rate. The fresh session was 157,198 of cacheRead against only 13,176 of cacheWrite. Cache reads bill at a tenth of base, and going cold barely touched that, because the TTL only applies to the session’s first turn; after that a session is reading its own cache.
Add the compaction itself back in and the two paths are not close:
- compact, then continue: $0.348 + $0.179 = $0.527
- start fresh instead: $0.089
Token counts and dollars point in opposite directions throughout this experiment. If you are optimizing by watching the context percentage in your status line, you are watching the wrong number.
What this does not settle
The follow-up question I used is answerable from the files. It needs no memory of the earlier conversation, which stacks the deck for the fresh session — it can rebuild everything it needs by reading. That is the honest boundary of the result: compaction buys you context you cannot cheaply re-derive, and my test question had none of that. Decisions you already made, approaches you already ruled out, the shape of a bug you spent an hour cornering — none of that is in the files, and paying $0.35 to keep it is obviously worth more than re-deriving it.
What the numbers do rule out is the idea that compacting is a cost optimization. It is not. It is a way to buy continuity, and it has a price.
Limits
- Sonnet 5 only. Opus has a different rate structure, so the ratio between the uncached re-read and the cached alternative will shift, though the mechanism will not.
- One context size, about 126k of conversation. The claim that compaction cost scales with how full the window is follows from the mechanism, not from a sweep across sizes — I measured one point on that line.
- Manual
/compactonly. Auto-compaction at the threshold is untested, and it may batch differently. - Headless
claude -psessions. Long interactive sessions may accumulate differently, though the compaction request itself is assembled the same way. - The dollar figures are what telemetry computes from list prices. On a subscription they are not what you are billed; treat them as a ratio, not an invoice.
- Three compaction runs. The
inputfigure was identical in all three, which is a stronger signal than three trials usually earns, but the summary length varied by 60%.
What I do now
I stopped treating /compact as housekeeping. It costs about half of what filling the window cost, it does not get cheaper by timing it well, and my transcript-based accounting was never showing it to me.
When the work ahead needs what the session already knows, compact and pay for it. When the next task is separable — a different file, a different bug, anything I could hand to a colleague with two sentences of setup — I start a session instead, and the two sentences cost less than the compaction would have.
The one piece of common advice I would now actively drop is compacting at the end of a session so it lands while the cache is warm. Three runs, one identical input figure, five minutes of TTL crossed in between: there is no warm path to catch.