claude-code-cache-keepalive
⚠️ EXPERIMENTAL — READ THIS BEFORE INSTALLING
This project is an experiment. It works by exploiting a currently-documented (but not contractually guaranteed) property of Anthropic's prompt cache: that cache reads refresh the 5-minute TTL. The economics assume you understand how it works. Used wrongly, this plugin will cost you more, not less.
Before you install, you need to understand at minimum:
- It is only useful on API-key / token-metered billing. On Pro/Max subscription plans, your quota is gated by request count, not token cost — every keepalive turn eats your 5-hour quota. On subscriptions, this plugin makes things worse. Disable it.
- Every keepalive turn is a real turn. It reads the cached prefix and produces a short reply. If your
keepalive_messagederails Claude into running tools or generating long output, the "cheap ping" stops being cheap.- The whole trick depends on one invariant (cache reads refresh the TTL). Anthropic can change that without notice — the same way they silently changed the default TTL from 1h to 5m in March 2026 (claude-code#46829). If they do, this plugin will quietly stop saving money and may start costing money.
- You must watch for community updates. Follow the linked issues and the CHANGELOG of this repo. When the community reports a behavior change on Anthropic's side, re-tune your
interval_seconds/max_loops_per_turnor disable the plugin entirely. A plugin you installed six months ago and forgot about is the exact scenario where this kind of trick silently turns against you.Read the Theoretical basis, The economics, and Caveats and warnings sections in full before enabling this. If any of those sections don't make sense to you yet, that's the signal that you are not yet ready to install this plugin. By installing, you accept that you have read and understood the mechanism and are responsible for monitoring its ongoing economics yourself.
A Claude Code plugin that keeps the prompt cache warm across idle
thinking pauses. It uses the Stop hook's decision: "block"
mechanism to inject a tiny keepalive turn just before the 5-minute
cache TTL expires. Cache reads cost ~12.5x less than cache
writes, so this saves real money on long sessions with idle gaps.
Why this exists. In March 2026 Anthropic silently lowered Claude Code's default prompt-cache TTL from 1 hour to 5 minutes. Users with normal 5+ minute thinking pauses started getting billed for full cache rewrites on every turn. See anthropics/claude-code#46829 for the original issue and the ~$2.5k of excess billing one user traced to the change. This plugin is a workaround.
Install
This repo ships as both a Claude Code plugin and a single-plugin marketplace. Inside Claude Code:
/plugin marketplace add yujiachen-y/claude-code-cache-keepalive
/plugin install cache-keepalive@claude-cache-tools
That's it. No daemon, no tmux, no Python, no curl, no API key.
To verify the plugin is active:
/plugin
and look for cache-keepalive in the enabled list. You can also tail
the hook log:
tail -f ~/.claude/cache-keepalive/cache-keepalive.log
How it works
When Claude finishes a turn, Claude Code fires the Stop hook. This
plugin's Stop hook script sleeps for interval_seconds (default
240s, safely under the 300s TTL) and then writes this JSON to
stdout:
{"decision":"block","reason":"got it, i need some time to think about the next move"}
Claude Code treats that as "the user sent a new instruction" and asks Claude to respond. The response reads the cached prompt prefix — and cache reads refresh the 5-minute TTL. Claude replies with one short line, Stop fires again, the loop continues.
┌─────────────────────────────┐
Claude finishes ───▶ │ Stop hook runs │
a turn │ counter < max_loops ? │
│ yes → sleep interval │
│ emit JSON: │
│ {"decision": │
│ "block", │
│ "reason": MSG } │
│ no → exit 0 (let Stop │
│ proceed normally) │
└──────────────┬──────────────┘
│ block
▼
┌─────────────────────────────┐
│ Claude Code receives block, │
│ injects MSG as new turn, │
│ Claude generates a reply. │
│ The reply READS the cached │
│ prefix → TTL is reset. │
└──────────────┬──────────────┘
│ Stop fires again
▼
loop ↻
When you actually want to type, hit Esc to interrupt the sleeping
hook. The UserPromptSubmit hook resets the loop counter for the
next turn. The SessionEnd hook cleans up on exit.
The keepalive turn is a real turn: it shows up in your
transcript and it hits the exact same cache_control block Claude
Code uses for the real prompt prefix. No reverse engineering, no API
hijacking, no Claude Code internals dependency.
Configuration
The plugin exposes three user-configurable values. When you install it, Claude Code will prompt you for each one. Leave a value blank to use the default.
| Key | Default | What it does |
|---|---|---|
interval_seconds | 240 | Seconds the Stop hook sleeps before pinging. Claude Code's TTL is 300s; 240 leaves a 60s safety margin. |
keepalive_message | got it, i need some time to think about the next move | Text injected as the user-side keepalive turn. Keep it bland so Claude replies cheaply. |
max_loops_per_turn | 7 | Max consecutive keepalive cycles per idle turn. At 240 × 7 ≈ 28 min, this caps coverage and prevents a runaway loop if you walk away. |
You can change these values any time via /plugin → select
cache-keepalive → reconfigure. Or override them at shell level with
CCKA_INTERVAL, CCKA_MESSAGE, CCKA_MAX_LOOPS — the hook scripts
check plugin config first, then these env vars, then the defaults.
Theoretical basis
This plugin rests on one well-documented but easily forgotten property of Anthropic's prompt caching:
A successful cache read refreshes the cache's TTL.
If your cached prefix would have expired in 30 seconds but you send any request that reads it, the 5-minute clock resets from scratch. That means a cheap "do nothing" request right before expiry keeps the cache alive for another 5 minutes — and you only pay for a cache read, not a cache write.
This is the same trick Aider's --cache-keepalive-pings, the
SillyTavern cache-refresh extension, and ClaudeMind's built-in pinger
all use. It was discussed for Cline in
cline/cline#414,
which is the clearest write-up of the math. From that thread
(cspotcode):
"If first message populates the cache at 1.25x cost, ping it 6 times at .1x cost, then send your second chat message...you still come out ahead: 1.25 + (6 + 1) × 0.1 = 1.95x cost for 2x requests."
That is: on a 6-minute idle gap, a naive session pays 2.5x base input cost (two full writes). A session with six 45-second-spaced pings pays only 1.95x — a ~22% savings on the idle gap, rising fast the longer the gap gets. ClaudeMind's implementation extends the effective TTL from 5 minutes to 60 minutes with ~12 pings and reports that the break-even is just two follow-up questions.
The economics (Claude Code specifics)
Anthropic's prompt caching pricing (Sonnet 4.6, per million tokens):
| Item | Multiplier | $/MTok |
|---|---|---|
| Base input | 1.0x | $3.00 |
| 5-min cache write | 1.25x | $3.75 |
| 1-hour cache write | 2.0x | $6.00 |
| Cache read | 0.1x | $0.30 |
A cache read is ~12.5x cheaper than a 5-min cache write for the same number of tokens. Each keepalive turn is one read over the existing prefix plus a ~20-token reply. At a 20K-token cached prefix:
- one keepalive turn:
20K × $0.30/M ≈ $0.006+ ~20 output tokens - one forced rewrite:
20K × $3.75/M ≈ $0.075
You can do roughly 12 keepalives before you've spent what a single
rewrite would cost. The default max_loops_per_turn=7 stays well
inside the break-even line.
For reference, the issue that prompted this plugin (anthropics/claude-code#46829) reports one user's traced excess billing at ~$949 on Sonnet-4.6 and ~$1,582 on Opus-4.6 after Anthropic silently flipped the default TTL from 1 hour to 5 minutes in March 2026 — with $1,198 of the Opus overcharge landing in a single month. Even at that user's scale, roughly two months of uncapped normal usage would have paid for a long time of running this plugin.
Caveats and warnings
-
Use API-key billing, not Pro/Max subscriptions. Subscription plans are gated on a 5-hour request quota, not on token cost. Extra keepalive turns eat your quota. On Pro/Max this plugin makes things worse, not better — disable it.
-
The keepalive turn is real. It shows up in your transcript, in your scrollback, and in your token history. Don't set
keepalive_messageto anything that derails Claude's reasoning or starts a long task. -
You have to know to hit
Esc. While the Stop hook is sleeping, Claude Code looks "stuck" — no input prompt. Press Esc once; the sleep exits and the prompt comes back. Hook logs in~/.claude/cache-keepalive/cache-keepalive.logconfirm what's happening. -
Anthropic could change the rules. This whole trick depends on "cache reads refresh the 5-minute TTL," which is currently true and documented but is not a contractual API guarantee. The same Anthropic that silently lowered the TTL default could change refresh semantics too. Don't bet anything important on this.
-
Hook runtime limits. Some Claude Code installs may warn about long-running hooks. The default 240s sleep is within typical timeouts, but if your config enforces stricter limits, lower
interval_secondsaccordingly. -
The
Stophook contract. This plugin relies on the documented{"decision":"block","reason":"..."}Stop-hook output. If a future Claude Code version changes that contract, the plugin will break loudly (keepalive disappears from the log) rather than silently leak budget.
File layout
claude-code-cache-keepalive/
├── .claude-plugin/
│ └── marketplace.json # the marketplace catalog
├── plugins/
│ └── cache-keepalive/
│ ├── .claude-plugin/
│ │ └── plugin.json # the plugin manifest + userConfig
│ ├── hooks/
│ │ └── hooks.json # Stop / UserPromptSubmit / SessionEnd wiring
│ └── scripts/
│ ├── cache-keepalive-stop.sh # the sleep-and-block loop
│ ├── cache-keepalive-reset.sh # clear counter on real prompt
│ └── cache-keepalive-cleanup.sh # clear counter on session end
├── LICENSE
└── README.md
Prior art
- Aider's
--cache-keepalive-pings— the same idea, implemented as a built-in feature inside Aider. This project ports the pattern to Claude Code via hooks and packages it as an installable plugin / marketplace. - Cache-Refresh-SillyTavern — same pattern for SillyTavern. Reports ~89% cost reduction on long conversations.
- cline/cline discussion #414 — community discussion of the same math for the Cline editor.
License
MIT. See LICENSE.