Claude Code Prompt Cache: Read the Hit Ratio, Set 5m or 1h TTLs

TL;DR
Claude Code now reports prompt cache hit ratio and miss causes in /usage. How to read it, script a status line, and set 5m or 1h TTLs.
Last updated: October 17, 2026
Claude Code now tells you, per session, how much of your input came from the prompt cache, how many turns missed it, and usually why. You get it in the "Prompt cache (main)" line of /usage (/cost is an alias) and in a prompt_cache object your status line script can read (v2.1.251, with a likely miss cause since v2.1.260). Separately, the promptCacheTtl and subagentPromptCacheTtl settings (v2.1.243) and per-agent experimental.cacheTtl frontmatter (v2.1.248) let you pick a 5-minute or 1-hour cache lifetime for each kind of request.
Our verdict: if you use an API key or a cloud provider and you step away mid-task, set promptCacheTtl to 1h and leave subagents on 5m. Everyone should put hit_ratio and last_miss_cause in their status line, because a cache miss is the most expensive thing that can happen to a long session, and until now you had to infer it from per-turn token counts.
To be precise about what is new: status line scripts could already read per-turn cache_creation_input_tokens and cache_read_input_tokens from current_usage, and the docs still document that. The new prompt_cache object adds session-level aggregation (hit ratio, miss count, tokens re-cached) and a diagnosis of why each miss happened.
This closes a gap we wrote about in May. In Claude Code's token burn problem is a cache observability problem we argued that /usage needed session-level cache telemetry. This post is the hands-on follow-up: what shipped, how to read it, and which knobs to turn.
What shipped, and when#
| Version | npm publish | Change |
|---|---|---|
| 2.1.243 | Aug 24, 2026 | promptCacheTtl and subagentPromptCacheTtl settings (the docs list 2.1.242 as the minimum) |
| 2.1.248 | Aug 27, 2026 | experimental.cacheTtl in subagent frontmatter |
| 2.1.251 | Aug 28, 2026 | Prompt cache line in /cost, prompt_cache status line object, PreModelSwitch / PostModelSwitch hooks, staleness and estimated re-cache cost in SessionStart resume hooks |
| 2.1.260 | Sep 3, 2026 | Likely cause for cache misses in /cost and the status line |
| 2.1.280 | Sep 22, 2026 | Miss causes now name thinking mode and thinking display changes |
| 2.1.300 | Oct 14, 2026 | OpenTelemetry api_request events split cache writes into cache_creation_5m_tokens and cache_creation_1h_tokens |
Check where you are and upgrade before following along:
claude --version
claude update
A new version usually changes the system prompt, so the first conversation after an upgrade starts uncached. Updates apply on the next launch, never mid-session, and resumed conversations keep their original system prompt by default, so upgrading does not disturb a session that is already running.
How the cache works in 60 seconds#
Prompt caching is an exact prefix match. Claude Code orders each request so the parts that rarely change come first:
| Layer | Content | Changes when |
|---|---|---|
| System prompt | Core instructions, tool definitions | The set of loaded tool definitions changes |
| Project context | CLAUDE.md, auto memory, unscoped rules | Session start, /clear, /compact |
| Conversation | Your messages, Claude's responses, tool results | Every turn |
A change in a layer forces everything after it to be reprocessed. A changed conversation turn leaves the system prompt and project context cached; a changed tool list invalidates everything. The model is also part of the key (each model has its own cache), and on most models so is the effort level.
Each request that hits the cache resets its timer, so the cache stays warm as long as you keep working. Go idle past the TTL and the next request reprocesses the whole input and writes it again. That is the "first turn back after coffee is slow and expensive" effect.
If you call the API directly and want to place cache_control breakpoints yourself, see our prompt caching production guide for the Claude API. Claude Code places them for you; this post is about observing and tuning what it does.
%% title: Where a Claude Code request hits or misses the cache
flowchart LR
A[New turn] --> B{Same model and tool set?}
B -- no --> M[Full miss: rewrite whole prefix]
B -- yes --> C{Within TTL since last request?}
C -- no --> M
C -- yes --> D{Prefix bytes identical?}
D -- no --> P[Miss from first changed block]
D -- yes --> H[Read prefix from cache]
H --> W[Write only the new exchange]Step 1: Read the Prompt cache (main) line#
Run /usage (or /cost, which is now an alias; /stats opens the Stats tab of the same screen). After the main conversation's first API response, the Session block includes a line like this example from the docs:
Prompt cache (main): 14 requests · 91% of input tokens from cache · 2 misses (last 6m 10s ago, 310.2k tokens re-cached) · 1 expected rebuild (compaction or tool-result clearing) · warm (1h TTL, last activity 40s ago)
Reading it left to right:
- 14 requests - API calls the main conversation has made this session.
- 91% of input tokens from cache - cache reads divided by all input, including reads, writes and uncached tokens. This is the same number the status line exposes as
hit_ratio. - 2 misses (last 6m 10s ago, 310.2k tokens re-cached) - how often a request had to rebuild something it should have been able to read, and how many tokens that cost to write again.
- 1 expected rebuild - compaction and tool-result clearing rewrite history by design, so they are counted separately instead of as misses.
- warm (1h TTL, last activity 40s ago) - whether the cache is still alive, and which TTL is in effect.
A miss has a precise definition: a request that re-processed more than 5% and at least 2,000 tokens of what it could have read from cache, with no compaction or tool-result clearing to explain it. Small wobbles do not count.
Three things to know about scope. The line covers the main conversation only, not subagents. /clear resets it. And because it is computed from the cache token fields in API responses, it works on every provider and gateway. If no response reported cache tokens, the line ends with "no prompt caching reported by the API", which means caching is disabled or something between you and the model is stripping the cache markers.
On v2.1.260 and later, a miss also carries a likely cause, for example likely cause: tool definitions changed. Step 3 maps those causes to fixes.
Step 2: Put cache health in your status line#
/usage is something you have to remember to run. The status line is always on screen, and since v2.1.251 Claude Code passes it a prompt_cache object on stdin.
Point your settings at a script (~/.claude/settings.json or project settings):
{
"statusLine": {
"type": "command",
"command": "~/.claude/statusline.sh"
}
}
Then save this as ~/.claude/statusline.sh and chmod +x it:
#!/bin/bash
# Prompt cache segment for the Claude Code status line (v2.1.251+, causes v2.1.260+)
input=$(cat)
MODEL=$(echo "$input" | jq -r '.model.display_name')
PC=$(echo "$input" | jq -c '.prompt_cache // empty')
if [ -z "$PC" ]; then
echo "[$MODEL] cache: n/a"
exit 0
fi
HIT=$(echo "$PC" | jq -r 'if .hit_ratio == null then "-" else ((.hit_ratio * 100 | round) | tostring) + "%" end')
STATE=$(echo "$PC" | jq -r 'if .warm then "warm " + .ttl else "cold" end')
MISSES=$(echo "$PC" | jq -r '.misses')
CAUSE=$(echo "$PC" | jq -r '.last_miss_cause.causes // [] | join(",")')
[ "$MISSES" = "1" ] && WORD="miss" || WORD="misses"
LINE="[$MODEL] cache $HIT $STATE, $MISSES $WORD"
[ -n "$CAUSE" ] && LINE="$LINE (last: $CAUSE)"
echo "$LINE"
We ran it with jq 1.7 against prompt_cache payloads built from the field reference in the docs. For the docs-style sample it prints:
[Opus] cache 91% warm 1h, 2 misses (last: tools_changed)
A 0.57 hit ratio with one miss prints cache 57% warm 5m, 1 miss (we use round because floor turns 0.57 into 56% through floating-point error). Before the first API response the object is absent and the script prints cache: n/a; after the cache expires it shows cold. It needs jq on your PATH.
The fields worth knowing:
| Field | What it tells you |
|---|---|
warm | Cache is still alive. False whenever the last response reported no cache tokens |
caching_observed | Any response has reported cache tokens at all |
ttl | "5m" or "1h" |
expires_at | Epoch seconds when the cache goes cold; null if the last response reported no cache tokens |
hit_ratio | Cache reads over all input; null while everything is zero |
misses / expected_rebuilds | Unexplained rebuilds vs compaction and tool-result clearing |
miss_recache_tokens | Tokens re-written because of misses |
last_miss_cause / miss_causes | Why the last miss happened, and a tally across the session (v2.1.260+) |
recache_tokens_if_cold | What the next request will have to re-write if the cache expires |
Two details make this more useful than polling /usage. First, the status line re-runs when a warm cache reaches its expires_at time, so the warm-to-cold flip shows up on screen without you sending a turn (updates are debounced at 300ms, and a refreshInterval setting re-runs it on a timer during idle periods). Second, recache_tokens_if_cold is the price tag on a coffee break: it is the number of tokens you will pay to write again if you let the cache lapse.
%% title: How cache stats reach /usage and the status line
sequenceDiagram
participant U as You
participant CC as Claude Code
participant API as Claude API
U->>CC: Prompt
CC->>API: Request with cache_control
API-->>CC: usage: cache_read, cache_creation
CC->>CC: Update prompt_cache stats and miss cause
CC-->>U: Status line script gets prompt_cache JSON
U->>CC: /usage
CC-->>U: Prompt cache (main) lineStep 3: Act on the likely cause#
last_miss_cause.causes holds one or more cause names. The object is null until the first miss and also when no cause could be identified. Here is what each documented cause usually means and what to do:
| Cause | Extra fields | Usual trigger | What to do |
|---|---|---|---|
tools_changed | tools_added, tools_removed | An MCP server connected or was disabled, a plugin with MCP servers toggled, or a bare deny rule (like Bash) added while tool search is off or unavailable | Keep tool search on; change MCP and plugin config between sessions, not mid-task |
system_prompt_changed | system_char_delta | Mostly different --append-system-prompt text, or the cases where Claude Code rebuilds the system prompt on resume (see system prompt flags in resumed conversations) | Keep system prompt flags identical across a task and its resumes |
ttl_expired_5m | - | You were idle longer than five minutes | Consider the 1h TTL (Step 4) |
likely_server_side | - | Nothing on your side changed | Note it and move on |
Since v2.1.280 the causes also name thinking mode and thinking display changes.
A few other documented invalidators that are worth building habits around:
- Model switches. Each model has its own cache, so
/modelmid-task rewrites everything. ThePreModelSwitchhook added in 2.1.251 can ask for confirmation before a switch; our Claude Code hooks guide covers how hook events are wired. - Effort changes. On most models each effort level has its own cache. On Opus 5.5, Sonnet 5.5, Haiku 5.5 and Fable 5.1 with an API key or a Claude subscription, changing effort keeps the cache (not on Bedrock, Google Cloud's Agent Platform or the Claude apps gateway).
- Fast mode. Turning it on adds a header that is part of the cache key. The cost is paid once per conversation, so turn it on early rather than deep into a long session.
- Images. When a request would exceed the image limits, Claude Code drops a batch of the oldest images, which reprocesses from the earliest affected message. You see one slow turn per batch.
- Custom gateways. The docs note that tool search is unavailable through a custom
ANTHROPIC_BASE_URLgateway, which makes MCP changes and bare deny rules cache-breaking there.
If you move a large session to the 1h TTL, also read what idle compaction does to big idle sessions: on the one-hour TTL, Claude Code can compact an idle conversation shortly before the cache expires.
Step 4: Choose 5m or 1h per bucket#
Claude Code assigns every request to one of two buckets:
- Main conversation: interactive turns,
-pruns, Agent SDK turns, and helpers that run inline with them. - Everything else: subagents, workflows, in-process teammates, forks, compaction and session titles.
The defaults depend on how you pay:
| Bucket | Claude subscription, within plan usage | Usage credits, API key, or cloud provider |
|---|---|---|
| Main conversation | 1h | 5m |
| Everything else | 5m (server-controlled helper requests get 1h) | 5m |
One subtle case: when a subscription goes over its plan limit and starts drawing on usage credits, the main conversation drops to 5m unless you have chosen a TTL yourself.
To choose, add the settings to any settings file:
{
"promptCacheTtl": "1h",
"subagentPromptCacheTtl": "5m"
}
Or use the environment variables CLAUDE_CODE_PROMPT_CACHE_TTL and CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL. Only "5m" and "1h" are accepted. Claude Code ignores any other value, so a typo like "60m" silently falls through to the next control in the precedence order (usually the bucket default, but experimental.cacheTtl or ENABLE_PROMPT_CACHING_1H wins if set).
When more than one control applies, the first match wins:
%% title: Which TTL a request gets (first match wins)
flowchart TD
R[Request] --> F{FORCE_PROMPT_CACHING_5M=1?}
F -- yes --> T5[5m]
F -- no --> E{Bucket env var set?}
E -- yes --> V[Use env value]
E -- no --> S{Bucket setting set?}
S -- yes --> SV[Use setting]
S -- no --> A{Subagent with experimental.cacheTtl?}
A -- yes --> AV[Use frontmatter value]
A -- no --> H{ENABLE_PROMPT_CACHING_1H=1?}
H -- yes --> T1[1h]
H -- no --> D[Bucket default]To confirm which TTL your main conversation actually wrote with, the docs give this check:
claude -p "hello" --output-format json | jq '.usage.cache_creation'
One-hour writes land under ephemeral_1h_input_tokens and five-minute writes under ephemeral_5m_input_tokens. We did not run this against a live key; it is the documented verification path.
The cost math#
These numbers are our arithmetic on Anthropic's published multipliers. On the API, 5-minute cache writes cost 1.25x base input, 1-hour writes cost 2x base input, and cache reads cost 0.1x base (0.05x on Opus 5.5 and Sonnet 5.5, and 0.025x on Fable 5.1). Opus 5.5 lists at $4 per million input tokens, $5 for a 5m write, $8 for a 1h write and $0.20 for a cache read.
Take a session whose cached prefix has grown to 300K tokens on Opus 5.5:
| Operation on 300K tokens | Cost |
|---|---|
| Write at 5m TTL | $1.50 |
| Write at 1h TTL | $2.40 |
| Read from cache | $0.06 |
The 1h premium is 0.75x base on every token written. In a healthy session each token is written roughly once, then read many times. A cold rebuild at 5m costs 1.25x on the whole prefix instead of a 0.05x read, so avoiding a single rebuild saves about 1.2x of the prefix. That one avoided rebuild more than covers the 0.75x premium you paid on everything written up to that point.
Rule of thumb: if you regularly pause for more than five minutes mid-task (reviewing a diff, a meeting, lunch), 1h is cheaper. For tight bursts that never idle, 5m is cheaper because you pay the higher write rate and never use the longer lifetime. The status line from Step 2 tells you which one you are: count your ttl_expired_5m misses over a week.
Step 5: Give one long-running subagent a 1h cache#
Subagents start their own conversation with their own system prompt and tools, do not read the parent's cache on their first request, and get 5m even on a subscription unless you choose otherwise. Forks are different: they inherit the parent's prefix and read its cache. If you are unsure which primitive you are using, see subagents vs agent teams vs workflows.
Since v2.1.248 you can give one agent its own TTL in its frontmatter:
---
name: repo-auditor
description: Audits a large repository and reports what it finds
experimental:
cacheTtl: 1h
---
The rules are strict:
- It must be nested under
experimental, not at the top level. - It is read only from subagent files. Definitions passed with
--agentsJSON ignore it. - A
1hvalue is ignored while a Claude subscription is using usage credits. - It ranks below
subagentPromptCacheTtlandCLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL. If you set the global subagent TTL in Step 4, it silently wins over this frontmatter. Use one or the other.
Good candidates are subagents that run long or get resumed: a resumed subagent's first request can read the cache the original run warmed. Short fan-out agents that finish in a minute should stay on 5m, since they pay the higher write rate for a lifetime they never use.
Gotchas and limits#
- Main conversation only. The
/usageline and theprompt_cacheobject exclude subagent requests. Subagent cache health is invisible there; use OpenTelemetry for it. caching_observed: falsemeans caching is disabled or a gateway is strippingcache_control.warmis false whenever the last response reported no cache tokens, so a single odd response can flip it.- Gateways and providers. Through an
ANTHROPIC_BASE_URLgateway, part of the 1h request travels in theanthropic-betaheader, which must be forwarded unchanged. The 1h TTL is not available through the Claude apps gateway. On Amazon Bedrock, caching support, minimum prefix length and 1h availability vary by model. FORCE_PROMPT_CACHING_5M=1beats everything, including managed settings. That makes it the documented way to A/B the two TTLs while debugging, and a surprise if you leave it exported in a shell profile.- Org-wide enforcement. TTL and caching environment variables can be set in the
envblock of managed settings. - Version skew. The changelog lists the TTL settings under 2.1.243 while the docs say 2.1.242 or later. Run a current build and it does not matter.
- Scope of the cache. The docs describe the cache as effectively scoped to one machine and directory, because the system prompt embeds auto memory paths and the conversation opens with the working directory and environment. Parallel sessions in the same directory build matching prefixes and read each other's cache.
- "Likely" is a heuristic. The cause is Claude Code's best inference, and
last_miss_causecan be null when nothing could be identified.
How it compares to the closest alternatives#
| Option | Setup | Scope | Miss diagnosis | Best for |
|---|---|---|---|---|
/usage line + prompt_cache status line | None (v2.1.251+) | Main conversation, one machine, live | Yes (v2.1.260+) | Individual developers |
Per-turn current_usage cache fields in the status line | A script | The last request only | No | Older builds, or a raw per-turn view |
| OpenTelemetry exporter | Collector required | Per user and session across an org, includes subagent requests; cache_read_tokens, cache_creation_tokens, and a 5m/1h write split since 2.1.300 | No | Platform and FinOps teams |
ENABLE_PROMPT_CACHING_1H=1 | One env var | Both buckets at once, lowest precedence | n/a | Older setups that want 1h everywhere |
Raw API / Agent SDK cache_control | Code | Whatever you build | Whatever you build | Custom agents (guide) |
The built-in line and OpenTelemetry are complementary rather than competing. The status line answers "why did that last turn cost so much" in the moment; OpenTelemetry answers "what is our team's cache efficiency this month" and is the only one of the two that sees subagents. The per-turn current_usage fields still work and are fine for spotting a single expensive turn, but you have to aggregate and diagnose misses yourself. Compared with ENABLE_PROMPT_CACHING_1H=1, the new settings let you split the main conversation from subagents, which is where most of the savings are: keep the long-lived conversation warm and let short-lived workers expire cheaply.
FAQ#
Where did the prompt cache line go if I type /cost?#
It is in /usage. /cost is now an alias for /usage, and /stats opens its Stats tab. The "Prompt cache (main)" line sits in the Session block and needs Claude Code 2.1.251 or later; the likely miss cause needs 2.1.260 or later.
Should I set promptCacheTtl to 1h on an API key?#
If you regularly pause more than five minutes mid-task, yes. One-hour writes cost 2x base input versus 1.25x for five-minute writes, and a single avoided cold rebuild covers that premium. For nonstop bursts that never idle, stay on 5m.
Why does my subagent's experimental.cacheTtl do nothing?#
Something higher in the precedence order is winning: subagentPromptCacheTtl, CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL, or FORCE_PROMPT_CACHING_5M=1. It is also ignored when it is not nested under experimental, in --agents JSON definitions, and for 1h while a subscription is on usage credits.
Will running claude update break my current session's cache?#
No. Updates apply on the next launch, never mid-session, and a resumed conversation keeps the system prompt it started with by default. The first new conversation after an upgrade usually starts uncached because the system prompt changed.
Does the prompt_cache object include subagents?#
No. It covers the main conversation only. For subagent cache usage, use the OpenTelemetry exporter, which reports cache tokens per user and session.
Sources#
- https://code.claude.com/docs/en/prompt-caching
- https://code.claude.com/docs/en/costs#prompt-cache-statistics
- https://code.claude.com/docs/en/statusline#prompt-cache-fields
- https://code.claude.com/docs/en/settings-reference#promptcachettl
- https://code.claude.com/docs/en/sub-agents#supported-frontmatter-fields
- https://code.claude.com/docs/en/env-vars
- https://code.claude.com/docs/en/commands
- https://code.claude.com/docs/en/cli-reference#system-prompt-flags-in-resumed-conversations
- https://code.claude.com/docs/en/setup#auto-updates
- https://code.claude.com/docs/en/monitoring-usage
- https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md
- https://platform.claude.com/docs/en/build-with-claude/prompt-caching#pricing
- https://www.npmjs.com/package/@anthropic-ai/claude-code
Continue Reading#
Get the next deep dive like this in your inbox
One email a week on Claude Code and the rest of the AI dev stack. Free.
Read next on Claude Code
Claude Code Token Burn Is an Observability Problem
Claude Code token burn is an observability problem. Why coding agents need cache-hit telemetry, spend ceilings and repro-grade usage logs, not a quota bar.
8 min readClaude Code Idle Compaction: What It Does and How to Turn It Off
Claude Code 2.1.299 compacts sessions over 200K tokens after about 54 idle minutes. How the cache math works, the setting to turn it off, and when to keep it.
8 min readPrompt Caching in the Claude API: A Production Guide
Cut Claude API spend by up to 90% with prompt caching. Real numbers, TypeScript SDK code, and the gotchas Anthropic's docs gloss over.
11 min readTechnical content at the intersection of AI and development. Building with AI agents, Claude Code, and modern dev tools - then showing you exactly how it works.








