Since v2.1.286 (released 2026-09-30), Claude Code compacts idle sessions of 200k+ tokens just before their prompt cache (the server-side copy of your context that makes repeat turns cheap) expires. That means your working context gets swapped for a summary, with no warning beyond the line “Compacted while idle, before the prompt cache expired”. You get a cheaper resume, but the session keeps only a summary: your exact instructions and earlier wording are gone. As of v2.1.290 you can opt out with one setting. If you build on the Claude API, the new on-demand compaction parameter offers the same trade with your code deciding when it happens and what survives.
What changed, version by version
| Version | Released | Change |
|---|---|---|
| 2.1.286 | 2026-09-30 | Idle sessions compact before the cache expires. No setting, no warning. The transcript mislabels it compactMetadata.trigger: "manual", as if you had run /compact |
| 2.1.290 | 2026-10-05 | New idleCompaction setting; the trigger is now labeled auto |
| 2.1.291 | 2026-10-06 | Reporter confirms "idleCompaction": false fixes it; issue closed |
None of this is in the Claude Code changelog. The details come from issue #98747 and an Anthropic maintainer’s replies in it. The reporter runs about 20 long-lived sessions in parallel. Overnight, 7 of them were compacted, none of them near the context limit. Their exact instructions, the reasons behind decisions, and the methods agreed on were gone from what the model sees. The maintainer said the feature fires only on contexts of 200k+ tokens.
Why it exists: a cold resume costs 25-40x a warm one
A prompt cache stores a request prefix so later requests can read it cheaply. An entry lives 5 minutes or 1 hour (one commenter on Max says Claude Code uses the 1-hour lifetime, or TTL). After it expires, the next turn has to write the whole context to the cache again.
Here is one turn’s cost to re-send a 200k-token context, computed from Anthropic’s list prices (MTok = million tokens):
| Model | Warm (cache read) | Cold, 5-min write (1.25x) | Cold, 1-hour write (2x) |
|---|---|---|---|
| Opus 5.5 ($4/MTok input) | $0.04 | $1.00 | $1.60 |
| Sonnet 5.5 ($2/MTok input) | $0.02 | $0.50 | $0.80 |
Cache reads on both models cost 0.05x the input price, so a cold turn costs 25x a warm one (5-minute write) or 40x (1-hour write). Compact before expiry and the next turn re-writes a short summary instead of 200k tokens. The summarization call still has to read the old context once, so the saving is the avoided cold write, not the whole turn. These are API list prices; on a subscription you don’t see them directly. Nobody in the issue disputes the cost logic. The complaint is the default: “the context is the valuable part, and a cache re-read is a cost I’d gladly pay to keep it.”
For how context fills up in the first place, see How Claude Code’s agentic loop fills your context window. For the cache rules behind the table, see Don’t break the prompt cache.
Fix: turn off idle compaction, keep threshold compaction
Symptom: you come back to a session and see “Compacted while idle, before the prompt cache expired”, and the session no longer follows instructions it had followed before.
Cause: the idle timer compacted a context of 200k+ tokens before its cache expired.
Solution: update to v2.1.290 or later and add the key to any settings.json: user (~/.claude/settings.json), project, local, or managed.
{
"idleCompaction": false
}
Threshold auto-compaction (the kind that fires when the context nears its limit) keeps working. Before v2.1.290 the only off switch was disabling auto-compaction entirely. Several people in the issue did that, which means hitting the context wall mid-task.
Two more details come from a commenter who read the 2.1.290 binary; the docs don’t list them. Setting "idleCompaction": true adds nothing, because a server-side gate still decides whether the feature runs. Turning auto-compaction off still turns idle compaction off too.
Keep it on, but steer what the summary keeps
If the cheaper resume is worth it to you, a PreCompact hook (a command Claude Code runs just before it compacts) can shape the summary. A commenter tested on 2.1.287 that idle compaction fires PreCompact with custom_instructions: null. Since 2.1.290 the trigger is auto, so a hook with the auto matcher catches both idle and threshold compaction:
{
"hooks": {
"PreCompact": [
{
"matcher": "auto",
"hooks": [
{
"type": "command",
"command": "echo 'Preserve verbatim: every standing instruction the user gave, each decision with its reason, and the task in progress.'"
}
]
}
]
}
}
Whatever the hook prints to stdout is appended to the summarizer’s instructions. The binary’s own hook description says so; the hooks reference page does not. Verify it the way the commenter did: print a canary token (“Begin the summary with PINEAPPLE-7731”), run /compact, and check the summary for it.
Three limits:
- Instructions are guidance, not retention. Nothing survives compaction word for word. The commenter found that after compaction Claude Code re-reads at most five recently touched files.
- No subagent filter. The
PreCompactinput hassession_id,transcript_path,cwd,trigger, andcustom_instructions, but noagent_id. Your instructions also reach subagents that auto-compact. - The transcript stays on disk. The pre-compaction history is still in the session’s JSONL file, which is how the reporter rebuilt their sessions by hand.
Same trade-off, your code decides: API on-demand compaction
The Claude API added on-demand compaction on 2026-09-14, in beta behind the compact-2026-09-04 header. You send the conversation with "compaction": {"type": "summarize"}. The API returns a single signed compaction block with stop_reason: "compaction" and no reply. From then on, that block goes first in messages, in place of the turns it summarized.
import anthropic
client = anthropic.Anthropic()
SYSTEM = "You are a coding assistant." # your conversation's system prompt
summary = client.beta.messages.create(
model="claude-opus-5-5",
max_tokens=4096, # caps thinking + summary, so leave room
system=SYSTEM, # same system prompt as the conversation (pass tools too if you use them)
betas=["compact-2026-09-04"],
messages=history,
compaction={
"type": "summarize",
"instructions": "Preserve every standing instruction, decision and reason, "
"and the open task. Do not call tools; reply with the summary only.",
},
)
if summary.stop_reason == "compaction":
history = [{"role": "assistant", "content": summary.content}]
Here, unlike idle compaction, you choose:
- When: you decide. Add
input_tokens,cache_read_input_tokens, andcache_creation_input_tokensfrom the last response and compact below your budget. - What it keeps:
instructions(up to 16,384 characters) replaces the default summarization prompt entirely. - Recent turns, word for word: send only the older turns to the compaction request, then put the block in front of the turns you kept. Cut where no tool call is left without its result.
- No pause: the request can run in the background while the conversation continues, and you swap the block in when it arrives.
- Cost: the summarization call is billed like any request and reported under
usage.iterations. Resending the block later costs nothing extra.
The older threshold mode (compact-2026-01-12, edit type compact_20260112) lets the API compact automatically once input reaches a trigger, 150,000 tokens by default and at least 50,000. To keep recent turns, set pause_after_compaction: true: the API stops with stop_reason: "compaction", you append the block, re-insert the turns, and resend. On the current models (Fable 5.1, Mythos 5.1, Opus 5.5, Sonnet 5.5, Haiku 5.5), threshold mode doesn’t carry the model’s reasoning (thinking blocks) forward, so strip thinking and redacted_thinking from turns you re-insert or the request returns a 400. On-demand compaction keeps that reasoning valid if the compaction request uses the conversation’s exact system and tools and the cut falls between a reply and the next user message.
Mistakes the API won’t catch, and the ones it will
| What you did | Result |
|---|---|
| Left summarized messages after the block | No error; Claude reads them twice |
| Dropped the block on a later request | No error; Claude gets no summary |
| Left summarized messages in front of the block | 400 compaction_block_misplaced |
Edited the block’s content or signature | 400 compaction_signature_invalid or compaction_content_mismatch |
| Omitted the beta header on a later request | 400 saying compaction isn’t an expected content block type |
Sent compaction and context_management together | 400; use one kind of compaction per request |
Images, documents, and fetched URLs inside the summarized range are gone once the block replaces them. Re-upload anything a later turn still needs.
Which compaction fits
| Claude Code idle | API on demand | API threshold | |
|---|---|---|---|
| Who decides when | Idle timer, 200k+ tokens | Your code | API, at an input-token trigger |
| Steer the summary | PreCompact stdout (undocumented) | instructions | instructions |
| Recent turns word for word | No | Yes | Yes, with pause_after_compaction |
| Kept thinking on 5.5 models | n/a | Yes, if system/tools match | No, strip it |
| Off switch | "idleCompaction": false | Don’t send it | Don’t set it |
Interactive sessions where you go away and come back: turn idle compaction off unless the cost of a cold resume actually hurts. Agents you build: compact on demand, keep the last few turns, and write your own instructions. For the client-side alternative, clearing old tool output instead of summarizing it, see Prune the log, not the window and observation masking.
Next step: add "idleCompaction": false to ~/.claude/settings.json now, then decide per project whether a PreCompact hook earns its place.