Claude Code cache TTL is now a frontmatter field
August 28, 2026
Claude Code cache TTL on a subagent still dies in five minutes unless that agent's own file says 5m or 1h. The main chat can sit on an hour, the reviewer you spawned does not, and a quiet gap past five minutes rewrites the worker's whole prefix while the parent stays warm. On billed traffic that rewrite is 1.25× or 2× the prefix. On a subscription inside plan usage the longer write is not a separate invoice, it still decides whether the next turn can reuse a warm cache.
What has to be true first#

The field does nothing on a stale CLI, and it also does nothing when a global subagent TTL is already set. Treat those as one prereq, not two nice-to-haves.
1. Pin 2.1.251 and clear the global subagent TTL#
A 2.1.247 session will happily store the YAML and then ignore it. experimental.cacheTtl shipped in 2.1.248. The /cost prompt-cache line and the status-line prompt_cache object shipped in 2.1.251. Pin the version that can prove the work.
claude --version
# 2.1.251 or laterThen look at the stack above the agent file. The prompt-caching docs take the first match in this order. FORCE_PROMPT_CACHING_5M=1 wins. Then CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL. Then subagentPromptCacheTtl in settings. Only then does experimental.cacheTtl on the file get a vote. ENABLE_PROMPT_CACHING_1H=1 is below that. The bucket default is last.
env | grep -E 'FORCE_PROMPT_CACHING_5M|CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL|ENABLE_PROMPT_CACHING_1H'
grep -n 'subagentPromptCacheTtl' ~/.claude/settings.json .claude/settings.json .claude/settings.local.json 2>/dev/null || trueEmpty grep on those two files is necessary, not sufficient. subagentPromptCacheTtl is valid in user, project, local, and managed settings. Check .claude/settings.local.json too, and any managed policy your org pushes. If any layer still sets it, every 1h agent file you write later is dead on arrival.
The settings reference even ships an example that pairs promptCacheTtl 1h with subagentPromptCacheTtl 5m. Fine for a global split. Fatal for per-agent frontmatter. Delete subagentPromptCacheTtl and unset CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL before you trust an agent file. The field only applies when no subagent TTL setting is configured, which is the 2.1.248 changelog's actual sentence, not a style note.
Two agent files, two lifetimes#

Subagents live in the "everything else" bucket. Claude Code's prompt-caching page is blunt about it. They get five minutes even on a Pro or Max plan until you choose a longer one. The first request never reads the parent's cache, because the prefixes differ. Each worker warms its own prefix across its own turns.
That is why a global 1h on every subagent is the wrong fix. Anthropic's prompt caching docs price a five-minute write at 1.25× base input and a one-hour write at 2×. A cache read is 0.1×. On a subscription inside plan usage the extra write rate does not invoice you, it still decides how long a quiet worker stays warm. On API-key or usage-credit traffic the 2× write is real money.
Use 1h where the next read comes after a gap longer than five minutes. A reviewer that sits between PRs is that agent. A chatty explorer that fires every few seconds is not. GitHub issue #74318 measured a blanket 1h on subagents at 8.6% more expensive on one heavy corpus. One developer, one codebase. Directionally it matches the price table. Do not copy the 8.6. Copy the split.
2. Give the reviewer a one hour cache#
You are looking at a Markdown file with YAML on top, not a settings key. The sub-agents docs put cacheTtl inside the experimental map. Top-level cacheTtl is ignored. Any value other than 5m or 1h is ignored. Claude Code reads the field only from subagent files, so --agents JSON will not carry it.
---
name: pr-reviewer
description: Reviews diffs for correctness, tests, and regressions. Use after writing or modifying code.
tools: Read, Grep, Glob, Bash
model: sonnet
experimental:
cacheTtl: 1h
---
You are a code reviewer. Report concrete findings. Stay in the diff you were handed.
Do not wander the repo unless the task says to.Project scope is .claude/agents/. User scope is ~/.claude/agents/. Project wins when both define the same name. If this is the first file in a brand-new agents directory, restart Claude Code so the watcher picks the directory up. Edits to a directory that already existed land in a few seconds.
3. Keep the chatty explorer on five minutes#
Do not copy the reviewer's 1h onto the explorer because it looks symmetric. Chatty agents reread their prefix inside seconds. Paying 2× to keep a cache that never goes cold is how the write premium leaks. Pin 5m so a later global ENABLE_PROMPT_CACHING_1H=1 cannot drag this worker onto the hour.
---
name: quick-explore
description: Fast read-only search through the repo. Use for targeted lookups and file discovery. Not for edits.
tools: Read, Grep, Glob
model: haiku
experimental:
cacheTtl: 5m
---
Search, quote paths, return. Do not write files.A user-level file named Explore would override the built-in Explore agent. That is a real lever if you want every automatic explore spawn on 5m. It is also how you accidentally replace the built-in with a worse prompt. Ship quick-explore unless you mean to take over the name.
Prove it with /cost and the status line#

You do not need a cache-hit harness. 2.1.251 prints the receipt in the session you already have. One /cost read is enough.
4. Spawn both and read /cost#
Ask Claude to use pr-reviewer on a diff and quick-explore on a path search. Then run /cost. The new line is labeled Prompt cache (main). It reports request count, percent of input tokens from cache, miss count with tokens re-cached, and warm or cold with the last activity age. Cold adds an estimate for the next rewrite.
Prompt cache (main): 12 requests · 91% of input tokens from cache · 1 miss (last 2m ago, 48k tokens re-cached) · warm (1h TTL, last activity 20s ago)That line is the parent session. It will not print pr-reviewer's TTL. The YAML is the worker proof. /cost tells you whether the conversation you are typing in is still warm after the spawns. If you see no prompt caching reported by the API, caching is off or the provider dropped the markers. That is a different fire.
5. Read prompt_cache from the status line#
The same 2.1.251 release added a prompt_cache object on the JSON piped into a status-line script. Neighbor post A Claude Code status line that shows weekly remaining already taught rate_limits. This object is not that object. Weekly remaining is a quota. prompt_cache is whether the prefix is still in the fridge.
{
"prompt_cache": {
"warm": true,
"caching_observed": true,
"ttl": "1h",
"expires_at": 1756400400,
"requests": 12,
"misses": 1,
"expected_rebuilds": 0,
"hit_ratio": 0.91,
"cache_write_tokens": 48000,
"miss_recache_tokens": 48000,
"last_miss_at": 1756399800,
"recache_tokens_if_cold": 120000
}
}Before the first API response the object is missing and a naive .prompt_cache.ttl will blank the bar. Same hitch as weekly remaining. Guard it.
#!/usr/bin/env bash
jq -r '
(.prompt_cache // {}) as $c |
if ($c | length) == 0 then
"cache wait"
else
($c.ttl // "?") as $ttl |
((($c.hit_ratio // 0) * 100) | floor | tostring) as $pct |
(if $c.warm then "warm" else "cold" end) as $w |
"cache \($ttl) \($pct)% \($w) misses \($c.misses // 0)"
end
'Point statusLine.command at that script in ~/.claude/settings.json, chmod +x it, and look for cache 1h 91% warm misses 1 under the prompt after a turn. The live status-line docs still describe current_usage.cache_creation_input_tokens / cache_read_input_tokens and have not caught up to prompt_cache. The 2.1.251 changelog is the page that names the object. Trust the changelog plus what /cost already printed in the same session.
When the field does nothing#

You will ship the 1h file, leave subagentPromptCacheTtl on 5m, and the reviewer will still write 5m caches. /cost and prompt_cache will not catch that. They only report the parent session. The proof for the worker is the YAML on disk, a clear precedence stack, and a spawn that actually uses pr-reviewer. Claude Code does not error when the file loses. It takes the first match in the stack and moves on.
subagentPromptCacheTtlorCLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTLset. Frontmatter loses. Unset them.FORCE_PROMPT_CACHING_5M=1in the environment. Everything is five minutes, including the reviewer.cacheTtlat the top of the YAML instead of underexperimental. Ignored.- A value like
30mortrue. Ignored. Only5mand1h. - Subscription overage. Docs and the CLI both ignore
1hon the agent while usage credits are running. --agentsJSON at launch. The field is file-only.- A new
agentsdirectory created after the session started. Restart once so the watcher sees it.
/model while the cache is still warm is a different class of miss. Each model has its own cache, so the next turn rereads the whole history even if TTL is 1h. Effort changes and turning fast mode on do the same. Prompt-caching docs list those as prefix breaks, not TTL expiry. Blocking the swap is a PreModelSwitch hook, which is a sibling setup, not this file.
Resume after a long idle is TTL expiry, and 2.1.251 finally tells a SessionStart hook about it. seconds_since_last_response, context_tokens, prompt_cache_likely_expired, and estimated_cache_write_usd land on resume and fork. The hooks reference page has not listed those keys yet. The 2.1.251 changelog did. If prompt_cache_likely_expired is true, the first turn re-caches. On Pro or Max, a large stale session also offers resume-from-summary so later turns do not drag the full history.
Older than 2.1.248 had a nasty hourly miss of its own. Tool definitions re-rendered after an OAuth refresh and dumped extended thinking with them. 2.1.248 fixed that. Upgrade before you debug frontmatter.
What you have now#
Done looks like two agent files, a /cost receipt, and a status-line read. Nothing else.
pr-reviewer.mdwithexperimental.cacheTtlset to1hquick-explore.mdwithexperimental.cacheTtlset to5m- A
/costline that names hit ratio, misses, tokens re-cached, and warm or cold - A status-line script that reads
.prompt_cache
The global subagent TTL is unset, so the files actually win. Token-trimming elsewhere still helps, and how to reduce AI coding tool token usage covers that job. This one is just the lifetime on the worker.
Cache TTL questions people actually asked
Should every subagent get a 1h cache like the main loop?
No. A blanket 1h on chatty subagents pays the 2× write for gaps that already fit in five minutes. Pin 1h on the reviewer that sits idle. Leave the explorer on 5m. experimental.cacheTtl is how you split them without a global setting that stomps both.
Does ENABLE_PROMPT_CACHING_1H actually reach subagents?
It requests one hour for both buckets, but it sits below env and settings, and it cannot split agents. Subagents historically missed the old Bedrock-only flag. Use experimental.cacheTtl on the file when you want 1h on one agent and 5m on another. Unset subagentPromptCacheTtl first or the file loses.
Why did sessions start rewriting the whole prefix after a 5 minute pause?
From 2.1.218 the client stopped asking for the 1h TTL on a lot of traffic, so a coffee break became a full cache write. 2.1.243 added promptCacheTtl and subagentPromptCacheTtl. 2.1.248 added the per-agent frontmatter field. Upgrade to 2.1.251 so /cost can show whether you are warm or cold.
Does /cost show the subagent's TTL?
The 2.1.251 line is Prompt cache (main). It reports hit ratio, misses, tokens re-cached, and warm or cold for the parent session. The agent file is the subagent proof. The status-line prompt_cache object is the live parent watch.
