anthropics / anthropics/claude-code
Feature request: prompt-cache keepalive while background tasks or permission prompts are pending
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 147k
- Forks
- 24k
- PR merge metrics
- PR metrics pending
Description
Summary
While a session is waiting on background work or a permission prompt, keep the prompt cache alive by re-sending the last request as an official max_tokens: 0 pre-warm before the TTL expires. Harness-owned, off by default, bounded by a spend cap. This is the keepalive that pi ships in its harness; in Claude Code only the harness sees the rendered request, so it cannot be built as a hook or extension.
Environment
- Claude Code 2.1.278, macOS 27.0, Max subscription
- Main conversation on the one-hour TTL (
ephemeral_1h_input_tokens: 57586fromclaude -p "hello" --output-format json, Opus 5)
Problem
A turn that ends with a background Bash, background Agent, Workflow, or Monitor pending leaves the session idle until that work finishes. A permission prompt does the same mid-turn. If the idle outlives the cache TTL, the next turn re-prefills the entire context at write price and full latency.
Two things make this common:
- Subagents, workflows, forks, and compaction get the five-minute TTL even on a subscription, and the main conversation drops to five minutes once usage credits apply.
- Background work routinely runs longer than either TTL. Test suites, builds, renders, and remote jobs are the normal reasons to background something.
The floor is not small: on this machine the system prompt plus tool definitions alone are 57,586 tokens, before any conversation. Every expiry during a long wait pays that again.
Related: #66115 (auto-compact on idle). That trades context for a cheaper miss. A keepalive keeps the context intact and avoids the miss. They are complementary, not alternatives.
Proposal
Ping. Keep an immutable snapshot of the last request as sent. When due, send it with max_tokens: 0 and stream: false, everything cache-relevant unchanged: model, tools, system, messages, cache_control markers and TTL, thinking config, effort, tool_choice, beta headers, routing. The pre-warm docs reject max_tokens: 0 only with stream: true, manual extended thinking, structured output, or forced tool_choice; adaptive thinking and effort are fine. The ping is not a turn: no tool execution, no hooks, no transcript append, no auto-continue, no conversation usage accounting.
When. Active while all three hold: resumable work is pending or a permission prompt is waiting; no real request is in flight; a valid snapshot exists. Parent conversation and each subagent keep their own snapshot and timer, since they sit in different TTL buckets.
Clock. nextPing = lastConfirmedCacheRequestStart + TTL - margin. The anchor is the start of the last request whose usage confirmed the expected prefix was read or written, not turn end. Failed requests do not advance it. After machine sleep, treat the cache as unknown until a real request confirms a prefix.
Stop. A real request takes over; the user sends a message with nothing pending; the spend cap is reached; the prefix drifts (compaction, model switch, effort or thinking change, tool list change, rewind); a ping reports an unexpected read size or a new write; 429 or network failure after one retry; session exit. At most one ping in flight.
Spend cap. Pinging breaks even against one re-prefill at roughly interval * (write_multiplier / read_multiplier - 1): about 46 minutes on the five-minute tier, about 17 hours on the one-hour tier at 0.1x reads. That horizon assumes the session resumes and hits, so it is a cap on ping spend, not a guarantee of savings. Default maxIdle to it.
Metrics. Count a hit only when the read covers the expected prefix size; a partial read with a large new write is a miss. Estimate benefit only on real resume, net of all ping spend in that wait. On subscriptions label it API-equivalent cost.
Config. cacheKeepalive: "off" | "on" (default off, experimental), plus maxIdle and margin, settable per bucket. Flip the default only after measured reuse and net cost justify it.
Acceptance
- A
max_tokens: 0replay on the real channel returnscontent: [], reads the full expected prefix, and writes nothing. - A background
sleeppast the TTL with keepalive on: on resume the old prefix hits and new writes cover only the appended reply and tool result. The same wait with keepalive off shows the baseline miss. - A permission wait past the TTL passes the same check.
- A ping never runs concurrently with a real request; compaction mid-wait drops the snapshot; a 429 stops after one retry.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
The issue names no repository files or tests. Start by tracing the request and cache lifecycle plus the background-task and permission-prompt entry points; then validate the acceptance scenarios for cache hits, concurrency, compaction, and retry behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, cli, performance
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100