Feature request: auto-compact on idle timeout to prevent cache expiry cost
Problem
When running a long operation (e.g. a test suite), users often step away from their Claude Code session. If they return after more than ~5 minutes, the prompt cache has expired. The full conversation context then has to be re-processed on the next turn, which is slower and more expensive than if the cache were still warm.
Requested feature
A configurable idle timeout that automatically triggers /compact before the prompt cache expires (i.e. before the ~5 minute TTL is up). This would reduce the context size proactively, so that when the user returns, re-processing the compacted summary is cheap — rather than re-processing the full conversation history cold.
Example configuration (sketch)
{
"autoCompactOnIdleSeconds": 240
}
When the session has been idle for the configured duration, Claude Code would trigger compaction automatically — similar to how auto-compaction works when approaching context limits, but time-triggered instead of token-triggered.
Why this matters
- Long CI/test runs are a common workflow where users step away
- Cache expiry during these pauses silently increases cost and latency on return
- Manual
/compactbefore stepping away works but requires the user to remember to do it
5 Comments
Good request, and the mechanism is real — I measured it on my own sessions to be sure. Across a sample of real session JSONL, the cached context re-read (
cache_read_input_tokens) is ~95% of all input token volume (cache_creation ~4.8%, genuinely new/uncached input ~0.2%). So the thing you're paying to rebuild after an idle gap really is the dominant cost, not a rounding error — when the ~5min TTL lapses, that ~95% gets re-established at the cache-write rate (~1.25× base) instead of the cache-read rate (~0.1× base) on the return turn. That's a real spike.Two design nuances worth flagging before this is built, because they change whether it actually saves money:
/compactis itself a billed model call, and it's lossy. It summarizes the conversation through the model, so it costs tokens every time it fires, and it permanently drops detail. The "stepped away during a long test run" case is exactly where you may go idle repeatedly — a naiveautoCompactOnIdleSecondscould fire several times in one sitting, paying the compaction cost each time and shredding context you still needed. To be a net win it'd need guards: fire at most once per idle period, only when the context is large enough that the saving exceeds the compaction cost, and ideally only when the cache would otherwise actually expire.(full_size − compacted_size) × cache_write_rate. Real, but it's not "cache stays warm," it's "the cold read is cheaper."On the workaround today: there isn't a native idle trigger to self-implement this — the hook events are PreToolUse/PostToolUse/Stop/etc., none of which fire on a time-based idle — so the manual
/compactbefore stepping away that you mentioned is genuinely the only current lever. That gap is a fair argument for the feature.One more, since cost is the motivation: because cache_read is ~95% of volume, the first-order cost driver is context size × turns, and idle-expiry is a secondary spike on top of that. If you want to quantify your own exposure before betting on this feature, sum
message.usage.cache_creation_input_tokensvscache_read_input_tokensacross your session's JSONL — the cache_creation share is what jumps right after an idle gap, so you can see exactly how many tokens (and dollars) the expiry actually costs you in your workflow.The idle-timeout compaction framing is interesting because it's really about the same root problem as threshold-based compaction, just triggered by time instead of token count: you want the session to be in a lean state before an expensive re-processing event (cache expiry in your case, context wall in the threshold case).
cozempic's guard daemon (github.com/Ruya-AI/cozempic) addresses the overlap: it prunes the session JSONL proactively before the cache expires or the threshold is hit, using strategies that strip aged tool outputs, metadata, and thinking blocks — so the context that gets re-processed after your test suite finishes is already trimmed. That's not the same as your proposed
autoCompactOnIdleSeconds: 240(it doesn't specifically trigger on idle), but the effect on cache-expiry cost is similar: a smaller context means cheaper re-processing on return, whether or not the session was idle.The gap in cozempic vs your proposal: it doesn't know about your workflow's natural idle boundaries (you stepped away for a CI run). A proper idle-timeout feature would let CC align the compaction event with when you intend to be away, which is more precise than a continuous threshold watcher. That's a real native feature worth having.
I ran into the same idle-expiry problem and ended up trying a different workaround than auto-compact: keeping the active Claude Code session’s prompt cache warm before the 5-minute TTL expires.
The approach I built is here, in case it helps anyone experimenting with this problem:
It is a VS Code extension rather than a Claude Code core feature. The basic flow is:
Stop/UserPromptSubmithooks into~/.claude/settings.jsonThe keep-alive message is deliberately constrained:
This does not replace the feature being requested here. Auto-compact before expiry would still be useful, especially for users who prefer reducing replay cost instead of sending keep-alive turns.
But for the specific “I stepped away / waited on tests / reviewed a diff for more than ~5 minutes and came back to a cold cache” case, this has been a practical workaround. It is pre-release and mostly Windows-tested so far, but the repo explains exactly what it changes on disk and how to remove it.
Alternatively, for the longer subscription TTL, it would be nice if Claude code auto compacted before the hour expires, and then users could pick if they want to pay the cost of resuming the full idle session, or resuming the compacted session.
There are open issues that are related:
https://github.com/anthropics/claude-code/issues/78245
https://github.com/anthropics/claude-code/issues/54822
https://github.com/anthropics/claude-code/issues/52002
The idle trigger is the right idea. The problem is that a single threshold like
240can fire after the prompt cache has already expired, and at that point compacting costs more than doing nothing.The costs break down like this. Resuming an untouched session costs
2C(full cold read). Compacting while the cache is warm costs0.1Cfor the summary plus2C'for the new smaller prefix. But if the cache expired before compaction ran, you pay the full2Cfor summarization and2C'on top. That loses on the first return.| scenario | cost to resume |
|---|---|
| leave it alone |
2C|| compact warm |
0.1C + 2C'|| compact cold |
2C + 2C'|Cold compaction isn't a total write-off. Each later warm turn saves
0.1(C - C'), so you break even after about six turns. But an idle session gives no signal whether the user is coming back for one quick thing or settling in. A late-firing default takes that gamble on their behalf.Cache TTL varies by account. My account is on a 1-hour tier (check your transcripts for
ephemeral_1h_input_tokensvsephemeral_5m_input_tokens). On that tier, 240s is unnecessarily early: it gives up conversation detail roughly 46 minutes before the cache would expire, and if the user walks back within that window the compaction threw away detail they could have resumed for free. If they stay away past expiry the compaction can still pay off over later turns, but that is the break-even case above, and it costs the loss of detail either way.A context floor matters too. I backtested 168 sessions over 45 days on my machine. Sessions holding under 100k when they went idle came back 68% of the time; above 500k, 98%. In this dataset, context size was enough on its own to rank reuse likelihood, and compacting small idle sessions offered little upside. Across 142 real compactions the measured ratio was
C'/C = 0.203.Suggested config shape:
"auto"would read from the account's observed cache tier. An internal upper bound would make a late tick a no-op instead of a cost.For anyone who wants this before it ships: it works today without new features. The VS Code extension runs the CLI over
--input-format stream-jsonon a socketpair, and a user message arriving that way has nooriginfield, so Claude Code treats it as human./compactsent through the extension'sclaudeCode.claudeProcessWrapperexpands normally. I documented the mechanism and the eight injection routes that don't work here: https://github.com/dthinkr/claude-code-auto-compactor