Skip to content

Browse catalog

Search catalog

[READY] Type a title, tag, or description.

Claude

Idle compaction trades your context for a cache write

AUTHOR
Bartłomiej Krupa
PUBLISHED
2026.10.08
READ_TIME
8 min

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

VersionReleasedChange
2.1.2862026-09-30Idle 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.2902026-10-05New idleCompaction setting; the trigger is now labeled auto
2.1.2912026-10-06Reporter 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):

ModelWarm (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 PreCompact input has session_id, transcript_path, cwd, trigger, and custom_instructions, but no agent_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, and cache_creation_input_tokens from 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 didResult
Left summarized messages after the blockNo error; Claude reads them twice
Dropped the block on a later requestNo error; Claude gets no summary
Left summarized messages in front of the block400 compaction_block_misplaced
Edited the block’s content or signature400 compaction_signature_invalid or compaction_content_mismatch
Omitted the beta header on a later request400 saying compaction isn’t an expected content block type
Sent compaction and context_management together400; 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 idleAPI on demandAPI threshold
Who decides whenIdle timer, 200k+ tokensYour codeAPI, at an input-token trigger
Steer the summaryPreCompact stdout (undocumented)instructionsinstructions
Recent turns word for wordNoYesYes, with pause_after_compaction
Kept thinking on 5.5 modelsn/aYes, if system/tools matchNo, strip it
Off switch"idleCompaction": falseDon’t send itDon’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.

FAQ

How do I stop Claude Code from compacting idle sessions?
Update to Claude Code v2.1.290 or later and add "idleCompaction": false to any settings.json (user, project, local, or managed). Threshold auto-compaction, the kind that fires when the context nears its limit, keeps working. Before v2.1.290 the only way to stop idle compaction was to turn off auto-compaction entirely.
Why does Claude Code compact a session I'm not using?
To avoid an expensive cold resume. A prompt cache entry lives 5 minutes or 1 hour. If you come back after it expires, the next turn re-writes the whole context to the cache at 1.25x or 2x the input price instead of reading it at 0.05x. On Opus 5.5 that is 25 to 40 times the cost of a warm turn. Compacting just before expiry shrinks what has to be re-written. It fires only on contexts of 200k+ tokens.
Does sending the API compaction block back cost extra?
No. With the Claude API's on-demand compaction (beta header compact-2026-09-04), only the summarization call is billed, reported under usage.iterations with top-level input_tokens and output_tokens at zero. Resending the signed compaction block on later requests adds no compaction cost.

Sources

  1. 2.1.286 idle compaction silently discards working context (anthropics/claude-code#98747)GitHub
  2. Compaction overviewAnthropic
  3. Compaction on demandAnthropic
  4. Compaction that keeps recent turnsAnthropic
  5. Compaction at a token thresholdAnthropic
  6. PricingAnthropic
  7. Claude API release notesAnthropic