●RELEASE — Claude Code v2.1.243 shipped on August 25 with broad improvements across usage reporting, model selection, sign-in, and reliability●USAGE — /usage now breaks results down per loop, showing run count, total tokens, tokens per run, and last run, which makes a chatty /loop task easy to spot●SETTINGS — modelPicker lets you curate the /model list with your own order and labels, while promptCacheTtl and subagentPromptCacheTtl let the main conversation and subagents keep different cache lifetimes●LOGIN — /login now offers keyless sign-in with an Anthropic Console account, so organizations that do not permit API keys can still get in●PERFORMANCE — The native binary is now zstd-compressed, dropping from roughly 340MB to 75MB on Linux x64, and each session uses about 40 to 60MB less memory●FIX — v2.1.245 resolves a startup crash on distributions shipping glibc 2.44, including Arch Linux, CachyOS, and Fedora Rawhide●RELEASE — Claude Code v2.1.243 shipped on August 25 with broad improvements across usage reporting, model selection, sign-in, and reliability●USAGE — /usage now breaks results down per loop, showing run count, total tokens, tokens per run, and last run, which makes a chatty /loop task easy to spot●SETTINGS — modelPicker lets you curate the /model list with your own order and labels, while promptCacheTtl and subagentPromptCacheTtl let the main conversation and subagents keep different cache lifetimes●LOGIN — /login now offers keyless sign-in with an Anthropic Console account, so organizations that do not permit API keys can still get in●PERFORMANCE — The native binary is now zstd-compressed, dropping from roughly 340MB to 75MB on Linux x64, and each session uses about 40 to 60MB less memory●FIX — v2.1.245 resolves a startup crash on distributions shipping glibc 2.44, including Arch Linux, CachyOS, and Fedora Rawhide
I decide the prompt cache TTL by where I come back to, not how long I was away
Claude Code v2.1.242 added promptCacheTtl and subagentPromptCacheTtl. Whether the one-hour cache pays off depends on returning to the same directory and the same git state, not on how long the break was. Here is the order I check things in.
Mornings in the iOS wallpaper app repo, afternoons on the web side, back into Xcode by evening. Running several products as an indie developer means moving between working directories many times a day. Every switch, Claude Code seemed to be re-reading the conversation from the top, and the waiting added up.
Claude Code v2.1.242 introduced two settings: promptCacheTtl and subagentPromptCacheTtl. They let you stretch the prompt cache lifetime from five minutes to one hour. My first reaction was that this would suit someone who steps away as often as I do.
Then I read the specification before writing the setting, and the deciding factor turned out not to be the length of the break. Whether the cache is still there when you return depends less on how long you waited and more on where you return to. If the destination has changed, stretching the TTL buys you a cache that never gets read once.
Two knobs, two buckets
Claude Code does not let you place cache_control yourself. It decides both the position and the boundaries. What you get to set is the lifetime, for requests split into two fixed buckets.
Bucket
Requests it covers
Setting key
Main conversation
Interactive turns, non-interactive -p runs, Agent SDK turns, and the helpers that run inline with them
Both accept exactly two values, 5m and 1h. Anything else is ignored. Write 30m and it is not rejected with an error; it quietly falls back to the default. That quiet fallback matters later, so keep it in mind.
The defaults, meanwhile, depend on how you are billed.
Bucket
Claude subscription, within plan usage
Usage credits, API key, or cloud provider
Main conversation
One hour
Five minutes
Everything else
Five minutes, except a small set of server-controlled helper requests that get one hour
Five minutes
While you are on a subscription and inside your plan's included usage, the main conversation already runs on the one-hour cache. The moment you go over the limit and start drawing on usage credits, that usage is billed to you, so Claude Code drops back to the cheaper five-minute TTL. Sign in with an API key or go through a cloud provider and everything starts at five minutes.
So writing promptCacheTtl explicitly is worth doing in two situations: you are on an API key or a cloud provider, or you are on a subscription and want to keep the hour after passing your plan's limit. If you are comfortably inside your plan, setting "1h" changes nothing that was not already true.
Subagents start at five minutes no matter what you pay
This is where I tripped first. On a subscription, the main conversation is running on a one-hour cache. Call a subagent and that child lands in the five-minute bucket. Exactly when you fan work out in parallel, the side that would benefit most from a warm cache is the side that starts cold.
The reasoning makes sense once you read it. A subagent opens its own conversation with its own system prompt and tool set, separate from the parent's. The prefixes differ, so it cannot read the parent's cache at all. It warms one of its own across its turns instead.
A fork inverts this. A fork inherits the parent's system prompt, tools, and conversation history exactly, so its first request reads the parent's cache. Two ways of splitting work, and from the cache's point of view they are not the same thing. I wrote about their broader differences in branching cuts loose, delegation stays in the session, but the caching behaviour alone is enough to change the choice sometimes.
Does that mean you should set subagentPromptCacheTtl to "1h"? I do not. One-hour writes are billed at a higher rate than five-minute writes, and subagents are short-lived — most finish their job in a handful of turns. There is no reason to pay the expensive write for something that disappears before it can earn it back.
The one case that flips this is calling the same subagent definition repeatedly within an hour. For that, I run the numbers through the break-even formula further down before deciding.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦You'll be able to tell, before flipping the switch, whether your own working rhythm can actually benefit from a one-hour cache
✦You'll understand why subagents and forks are treated differently by the cache, and pick the right one when you split work
✦You'll avoid the quiet days where a setting is in place but doing nothing, by checking precedence and unsupported environments up front
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
This is the finding that made me want to write the article.
Claude Code's cache is effectively scoped to one machine and one directory. The system prompt embeds the working directory, the platform, the shell, the OS version, and the auto memory paths. Different directory, different prefix. Different prefix, different cache.
Several consequences follow directly, and they hit daily work.
Two worktrees of the same repository do not read each other's cache. Each worktree is its own working directory
Sessions running in parallel in the same directory build matching prefixes and do read each other's cache
Sessions opened one after another in the same directory share a prefix only when the git status snapshot at startup matches, because the system prompt also captures the branch and recent commits
That third one caught me. On the web side, I add an article, commit, step away, and come back inside the hour. On the TTL clock, I made it. But the recent commits have changed, so the prefix the next session assembles is not the one I left. I bought a one-hour cache and then walked to an address that no longer exists.
Deciding "1h" from the length of my breaks had the order backwards. The first thing to look at is whether you return to the same directory in the same git state. If you do not, the extended TTL leaves nothing behind but the higher write rate.
How to structure the multi-repository work itself is a separate topic, and keeping the cache warm while moving between repositories goes into that. This article stays one step earlier, on the decision of whether to stretch at all.
Make the read-to-write balance visible
Before deciding anything, it helps to see where you currently stand. The API reports two token counts on every response, and a status line script can read them.
Field
Meaning
cache_creation_input_tokens
Tokens written to the cache this turn, billed at the cache write rate
cache_read_input_tokens
Tokens served from the cache this turn, billed at roughly 10% of the standard input rate
Save the script below as ~/.claude/cache-ratio.sh and make it executable. It prints the share of this turn's input that came from cache, plus how many tokens were read per token written.
#!/bin/bash# Claude Code status line: show the cache balance of the most recent API call.# The session JSON arrives on stdin.input=$(cat)MODEL=$(echo "$input" | jq -r '.model.display_name // "?"')# current_usage is null before the first API call of a session and right after# /compact. Without fallbacks, the arithmetic below dies on an empty string.READ=$(echo "$input" | jq -r '.context_window.current_usage.cache_read_input_tokens // 0')WRITE=$(echo "$input" | jq -r '.context_window.current_usage.cache_creation_input_tokens // 0')FRESH=$(echo "$input" | jq -r '.context_window.current_usage.input_tokens // 0')TOTAL=$((READ + WRITE + FRESH))if [ "$TOTAL" -eq 0 ]; then echo "[$MODEL] cache: --" exit 0fi# Share of this turn's input that came from cache, integer math only.HIT=$((READ * 100 / TOTAL))# Reads per token written, scaled by 10 to keep one decimal place.if [ "$WRITE" -gt 0 ]; then RATIO=$((READ * 10 / WRITE)) RATIO_STR="$((RATIO / 10)).$((RATIO % 10))x"else RATIO_STR="inf"fiecho "[$MODEL] cache ${HIT}% hit | read/write ${RATIO_STR} | fresh ${FRESH}"
Note that current_usage sits under context_window, not at the top level. Get that wrong and jq returns null silently, the fallbacks kick in, and you get cache: -- forever. A script that looks alive while reporting nothing is the worst failure mode of the three.
Test it with mock input before wiring it up. Here are four cases run locally.
$ echo '{"model":{"display_name":"Opus"},"context_window":{"current_usage":{"input_tokens":800,"output_tokens":1200,"cache_creation_input_tokens":5000,"cache_read_input_tokens":48000}}}' | ./cache-ratio.sh[Opus] cache 89% hit | read/write 9.6x | fresh 800# Right after the prefix broke$ echo '{"model":{"display_name":"Opus"},"context_window":{"current_usage":{"input_tokens":800,"output_tokens":1200,"cache_creation_input_tokens":52000,"cache_read_input_tokens":0}}}' | ./cache-ratio.sh[Opus] cache 0% hit | read/write 0.0x | fresh 800# current_usage is null (before the first API call, or just after /compact)$ echo '{"model":{"display_name":"Opus"},"context_window":{"current_usage":null}}' | ./cache-ratio.sh[Opus] cache: --# context_window missing entirely$ echo '{"model":{"display_name":"Sonnet"}}' | ./cache-ratio.sh[Sonnet] cache: --
The second case is the one to watch for. If turn after turn shows zero reads and large writes, the problem is not the lifetime — your prefix is changing every time, and no TTL will fix that.
Once the gap outlives the TTL, the cache becomes a surcharge
Here is where my prediction was exactly backwards.
Relative to the base input token price, five-minute writes cost 1.25x, one-hour writes cost 2x, and reads cost 0.1x. A write costing more than the base price means that a cache entry nobody reads is pure overhead.
I wanted to see how much overhead, so I put the published multipliers into a normalised model: an eight-turn session, a 40,000-token prefix at the start, and 1,500 tokens of growth per turn. These are computed figures from public rates, not a real invoice.
# Normalised so that one base input token = 1.0W5, W1H, READ = 1.25, 2.0, 0.1def session_cost(turns, gap_min, prefix0, delta, ttl_min, write_mult): """Total input cost for one session, assuming the conversation grows by delta per turn.""" prefix, total = prefix0, 0.0 for i in range(turns): warm = (i > 0) and (gap_min <= ttl_min) # within the TTL of the previous turn? if warm: total += READ * prefix + write_mult * delta # read the old part, write the increment else: total += write_mult * (prefix + delta) # rewrite the whole prefix prefix += delta return total
Varying only the gap between turns:
Gap between turns
No cache
5m TTL
1h TTL
Cheapest
2 minutes
374,000
97,200
136,200
5m
10 minutes
374,000
467,500
136,200
1h
45 minutes
374,000
467,500
136,200
1h
90 minutes
374,000
467,500
748,000
No cache
Look at the ten-minute row. The five-minute TTL costs 467,500 against 374,000 for no caching at all. The cache expires every five minutes, so every turn becomes a full rewrite, and every rewrite bills at 1.25x. A cache that is never read is exactly a 25% surcharge.
And five minutes is the default for the main conversation when you sign in with an API key. Anyone working in ten-minute bursts with breaks in between may be paying that 25% without noticing. My instinct that this setting would help me was right, but the reason was not that an hour is cheap. It was that five minutes was expensive.
The ninety-minute row twists once more. Stretching to an hour lands at twice the no-cache cost. When even an hour cannot bridge the gap, every turn is a 2x rewrite, so the longer lifetime sinks you further. A longer TTL is not a safer TTL.
The break-even is easy to state. With a write multiplier of W and reads at 0.1, a cache pays for itself after the smallest whole number of requests above (W − 0.1) ÷ 0.9. For five minutes that is 1.28, so the second request already wins. For one hour it is 2.11, so you wait until the third. A one-hour cache only earns its keep when the same prefix is read at least twice.
I would not jump from this table to turning caching off, though. The documentation recommends leaving it enabled for normal use, and caching buys latency as well as money. What I took from the exercise was not that caching is optional, but that a mismatch between your rhythm and your lifetime is expensive in a way that never announces itself. If you call the API directly and can place your own breakpoints, splitting the TTL into two tiers gives you far more room than these two keys do.
The order I check things in
Turning all of that into the sequence I actually use. Work down the list and stop at the first row that decides it.
#
What to check
What it settles
1
Are you on a subscription and inside your plan's usage?
If so, the main conversation is already on an hour. Nothing to write
2
When you resume, do you return to the same directory in the same git state?
If not, stay on 5m. A longer lifetime will never be read
3
Do gaps between turns exceed five minutes but stay under an hour?
If yes, 1h on the main conversation is worth it
4
Will the same prefix be read at least twice within that lifetime?
If not, only the higher write rate remains
5
Do you re-invoke the same subagent definition several times an hour?
If so, consider 1h for subagentPromptCacheTtl too
Moving the second row near the top is the change I made. I used to start from the gap length in row three, and that produced a steady stream of decisions to stretch.
There is one more option when you leave a long conversation behind. On Pro and Max plans, resuming a large session after a long break prompts you to resume from a summary, so later requests stop carrying the whole history. Sometimes that helps more directly than extending any lifetime.
Clear the "set but not working" cases first
TTL values can arrive from several places, and most of the time a setting that appears to do nothing is simply losing to something above it. Claude Code takes the first match in this order:
FORCE_PROMPT_CACHING_5M=1 — forces five minutes for both buckets
The bucket's environment variable (CLAUDE_CODE_PROMPT_CACHE_TTL / CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL)
The bucket's setting key (promptCacheTtl / subagentPromptCacheTtl)
ENABLE_PROMPT_CACHING_1H=1 — requests one hour for both buckets
The bucket's default
The setting key is third. If a stale value sits in managed settings or an environment variable, nothing you put in settings.json will reach the request. Read the other way round, FORCE_PROMPT_CACHING_5M=1 outranks everything, which makes it the right tool for comparing the two lifetimes or temporarily overriding a longer TTL pushed by managed settings.
Some environments cannot give you the hour at all.
The one-hour TTL is not available through a Claude apps gateway session
On Amazon Bedrock, caching support, the minimum cacheable prefix length, and one-hour TTL availability all vary by model. If your cache token counts stay at zero, check the supported models and regions first
Behind a custom ANTHROPIC_BASE_URL or an LLM gateway, whether caching works at all depends on the gateway
And the property from the opening comes back here. Only 5m and 1h are accepted; everything else is ignored. Write "60m" and you get no warning — it falls back to the default and keeps running. After changing the setting, judge it by whether the read-to-write ratio in your status line moved, not by what the file says. The file will happily tell you nothing is wrong.
The one thing to check first
Before adding a TTL setting, drop cache-ratio.sh in place and watch it for half a day. If reads sit at several times writes, your current configuration is already in step with how you work. If writes keep dominating, the thing to fix is not the lifetime but the prefix that keeps changing underneath it.
I had that order reversed myself and wrote the setting before looking at any numbers. Had I checked first, I would have found there was nothing to stretch. If this saves someone the same detour, that would make me glad.
Share
Thank You for Reading
Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.