Claude Code Prompt Caching Explained: Read the /usage Cache Line, Pick the 5-Minute or 1-Hour TTL, and Stop Breaking the Prefix

Claude Code prompt caching guide: read the /usage Prompt cache (main) line, pick a 5m or 1h TTL per bucket in settings.json, and fix what breaks the prefix.

Illustrative horizontal bar chart of one Claude Sonnet 5.5 request carrying a 100,000-token prefix at list prices: cache read $0.01, uncached input $0.20, 5-minute cache write $0.25, 1-hour cache write $0.40.Illustrative horizontal bar chart of one Claude Sonnet 5.5 request carrying a 100,000-token prefix at list prices: cache read $0.01, uncached input $0.20, 5-minute cache write $0.25, 1-hour cache write $0.40.
Illustrative: at Sonnet 5.5 list prices, rewriting a 100,000-token prefix costs 25 times a cached read at the five-minute rate and 40 times at the one-hour rate. Subscription plan usage is not billed per token. Source: Claude Platform Docs pricing page, read Nov 5, 2026.

Claude Code makes a new API request for every message you send, and each request carries the whole session again: the system prompt, the project context it loaded, every earlier turn and tool result. Claude Code prompt caching is what keeps that affordable. When the start of a request matches content the API processed recently, the repeat is billed at the cached rate. When it does not match, the full history is processed again and written back to the cache. You feel the difference as a slow first turn after lunch, a plan window that empties faster than your activity explains, or an API bill where a quiet afternoon cost as much as a busy morning.

You no longer have to guess which case you are in. On Oct 31, 2026, Ethan Lin pointed out on X that /usage now prints a “Prompt cache (main)” line with the share of input tokens served from cache, the misses (often with a likely cause), and whether the cache is warm or cold. That post is the hook. Anthropic’s documentation is the authority for everything below.

This guide is written for a developer who uses Claude Code most days, either on a Pro, Max, Ultra, Team or Enterprise subscription or on an API key, Amazon Bedrock, Google Cloud’s Agent Platform or Microsoft Foundry, and who suspects the cache is not doing its job. By the end you will be able to read the cache line in /usage and the status line fields behind it, confirm from claude -p JSON which time to live (TTL) a request actually used, set promptCacheTtl and subagentPromptCacheTtl deliberately, and stop the habits that quietly force a rebuild. Session numbers marked Illustrative are worked examples in the documented format, not output captured from a real run.

How the prefix cache decides what you pay

The model keeps nothing between requests, so Claude Code re-sends everything each turn and appends the new material at the end (How Claude Code uses prompt caching). The API caches by matching the start of each request, called the prefix, against content it processed recently. On a normal turn the prefix is the whole previous request and only the latest exchange is new. The match is exact. A change anywhere in the prefix recomputes everything after it, and there is no per-file or per-segment caching to soften the blow.

Screenshot of the top of Anthropic's Claude Code Docs page titled How Claude Code uses prompt caching.
The opening of Anthropic's "How Claude Code uses prompt caching" documentation page as captured. Source: Claude Code Docs, code.claude.com. · Original source

Claude Code orders each request so the content least likely to change comes first:

Layer What it holds Changes when
System prompt Core instructions and tool definitions The set of loaded tool definitions changes
Project context CLAUDE.md, auto memory, unscoped rules Session start, /clear or /compact
Conversation Your messages, Claude’s replies, tool results Every turn, appended at the end

That ordering does most of the work. A new turn only touches the bottom layer, so the system prompt and project context are read from cache. A change to the system prompt invalidates everything, because every later token now sits behind a different prefix. Two things sit outside the table: each model has its own cache, and on most models each effort level has its own cache as well. If you want to see how big each layer is, the context window guide walks through /context; this guide is about whether those tokens are re-read cheaply.

Diagram of one Claude Code request drawn as four boxes in a row, System prompt, Project context, Conversation and New turn, with three cards below: Rebuilds all points to System prompt, Rebuilds history points to Conversation, and Keeps the prefix points to New turn.Diagram of one Claude Code request drawn as four boxes in a row, System prompt, Project context, Conversation and New turn, with three cards below: Rebuilds all points to System prompt, Rebuilds history points to Conversation, and Keeps the prefix points to New turn.
A change in one layer recomputes every layer to its right: tool-definition changes rebuild the whole request, compaction rebuilds the history, and skills, plan mode, /recap, /rewind and subagent calls only append. Original diagram based on Anthropic's Claude Code prompt caching documentation, read Nov 5, 2026.

The price gap is what turns a miss from an annoyance into a cost. Anthropic’s pricing page prices a five-minute cache write at 1.25 times the base input rate, a one-hour cache write at 2 times, and a cache read at 0.1 times, with lower read multipliers on the newest models: 0.05 times on Claude Opus 5.5 and Sonnet 5.5, and 0.025 times on Claude Fable 5.1 and Mythos 5.1. On Sonnet 5.5 that is $2 per million input tokens, $2.50 for a five-minute write, $4 for a one-hour write and $0.10 for a read.

Illustrative horizontal bar chart of one Claude Sonnet 5.5 request carrying a 100,000-token prefix at list prices: cache read $0.01, uncached input $0.20, 5-minute cache write $0.25, 1-hour cache write $0.40.Illustrative horizontal bar chart of one Claude Sonnet 5.5 request carrying a 100,000-token prefix at list prices: cache read $0.01, uncached input $0.20, 5-minute cache write $0.25, 1-hour cache write $0.40.
Illustrative: at Sonnet 5.5 list prices, rewriting a 100,000-token prefix costs 25 times a cached read at the five-minute rate and 40 times at the one-hour rate. Subscription plan usage is not billed per token. Source: Claude Platform Docs pricing page, read Nov 5, 2026.

Put a 100,000-token session on Sonnet 5.5 through those prices and a warm turn re-reads the prefix for about one cent, while a cold turn rewrites it for 25 cents at the five-minute rate. Each request that hits the cache also resets its timer, so a session you keep working in stays warm; the expensive moments are the first turn after a gap longer than the TTL and any action that changes the prefix. If you run long unattended jobs and want a dollars-per-job ledger rather than a per-session view, the Fable 5.1 overnight meter builds one from the same usage fields.

On a subscription, plan usage is not billed per token, and the costs page says the dollar figure in the /usage Session block is intended for API users. Misses still matter there: the same page lists cache misses after a break longer than the cache lifetime among the main reasons usage climbs in a long session.

Where the cache lives depends on how you sign in. With an API key, a Claude subscription or Claude Platform on AWS it sits in Anthropic’s infrastructure; on Bedrock or Google Cloud’s Agent Platform it sits with your cloud provider; behind a custom ANTHROPIC_BASE_URL or LLM gateway, whether caching works at all depends on the gateway. The cache is also effectively scoped to one machine and one directory, because the system prompt embeds your auto memory paths and the conversation opens with the working directory, platform, shell and OS version. Parallel sessions in the same directory build matching prefixes and can read each other’s cache, which matters if you already split work across parallel sessions. Sequential sessions share a prefix only when the git status snapshot taken at startup matches.

Before you start: versions, billing and tools

The cache features arrived over several releases, so check your version with claude --version before trusting an empty field. Work through this list once:

  • [ ] Claude Code v2.1.242 or later, for promptCacheTtl, subagentPromptCacheTtl, CLAUDE_CODE_PROMPT_CACHE_TTL and CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL.
  • [ ] v2.1.251 or later, for the Prompt cache (main) line in /usage and the prompt_cache object in status line input.
  • [ ] v2.1.260 or later, for the likely-cause text, last_miss_cause and miss_causes.
  • [ ] v2.1.305 or later, if you want the f shortcut that applies /usage recommendations on a plan.
  • [ ] jq installed, for the status line script and the claude -p check.
  • [ ] Write access to ~/.claude/settings.json, or to the project settings you share with your team.
  • [ ] A clear answer to “how am I billed right now”: subscription within plan usage, subscription drawing usage credits, API key, or a cloud provider. The TTL defaults differ for each.

Upgrading has its own one-time cost. A new version typically changes the system prompt or tool definitions, so the first conversation after an upgrade builds its cache from the top. Auto-update applies new versions on the next launch, never mid-session, so schedule the restart between tasks rather than in the middle of one.

Read the Prompt cache (main) line in /usage

After the main conversation’s first API response, /usage adds a Prompt cache (main) line to its Session block. The costs documentation shows this example:

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)

Read it left to right:

Part of the line What it means What it tells you to do
14 requests API requests recorded for the main conversation this session Gives the ratio its sample size; a handful of requests is too few to judge
91% of input tokens from cache Cache reads as a share of all input: reads, writes and uncached input together In a long session most input should be re-reads; a share that stays low points at prefix churn
2 misses (last 6m 10s ago, 310.2k tokens re-cached) Requests that reprocessed content the cache already held: more than 5% and at least 2,000 tokens of what could have been read, with no compaction or tool-result clearing to explain it The re-cached count is what those misses wrote back at the write rate
likely cause: tool definitions changed Shown when Claude Code can identify the cause of the last miss (v2.1.260 or later) Match it against the habits table later in this guide
1 expected rebuild A rebuild after Claude Code itself compacted or cleared old tool results Not a fault; appears only after at least one such rebuild
warm (1h TTL, last activity 40s ago) The prefix is still within its lifetime, and which TTL is in effect When it says cold, it shows how long the session has been idle; the next turn rewrites the prefix
no prompt caching reported by the API No response has reported cache tokens Go straight to the troubleshooting table

Use it in this order:

  1. Send at least one prompt, then run /usage. The line does not exist until the main conversation has had a response.
  2. Note the hit share, the miss count and the TTL in the warm or cold part.
  3. If there are misses, read the likely cause before changing anything. Different causes have opposite fixes.
  4. Take your reading before /clear, which resets the line along with the rest of the Session block.
  5. On a Pro, Max, Ultra, Team or Enterprise plan, scroll to the recommendations. Each row covers at least 10% of recent usage, and the documented behaviors behind them include long context and cache misses.
  6. When the footer shows a hint such as f to fix 2 of 3, press f. Claude Code runs any row that ends in an /effort command, then gives Claude one prompt for the other rows it can act on and asks it to show each change before making it. Rows that end in run /compact are yours to run.

Two limits are easy to miss. The line covers the main conversation only, not subagents. And while Remote Control is connected, the footer shows no f hint at all.

Worked diagnosis: one session, two kinds of miss

Here is a filled example. The developer works on an API key with Sonnet 5.5, so the main conversation is on the five-minute default. Their session loads tools upfront rather than deferring them (the docs give a Microsoft Foundry deployment hosted on Azure that rejects tool search as one such case), and they have two habits: they connect a documentation MCP server partway through a task and remove it afterwards, and they step away for six to ten minutes while the integration tests run. The numbers are Illustrative, written in the documented format; the exact wording and placement of the likely-cause text on your build may differ.

Before:

Prompt cache (main):   38 requests · 61% of input tokens from cache · 7 misses (last 2m 40s ago, 840.0k tokens re-cached) · likely cause: tool definitions changed · warm (5m TTL, last activity 25s ago)

The status line input for the same moment carries the breakdown that the summary line compresses (Illustrative values, documented field names):

{
  "prompt_cache": {
    "warm": true,
    "caching_observed": true,
    "ttl": "5m",
    "requests": 38,
    "misses": 7,
    "expected_rebuilds": 0,
    "hit_ratio": 0.61,
    "miss_recache_tokens": 840000,
    "last_miss_cause": { "causes": ["tools_changed"], "tools_added": 3, "tools_removed": 0 },
    "miss_causes": { "tools_changed": 4, "ttl_expired_5m": 3 },
    "recache_tokens_if_cold": 124000
  }
}

Seven misses rewrote about 120,000 tokens each. The miss_causes map shows two separate problems hiding behind one hit ratio, and each needs its own fix:

Finding Evidence Cause Change Expected observation
Four misses from tool changes tools_changed: 4, last miss added 3 tools With tools loaded upfront, a server that connects adds definitions to the system prompt, and removing it on purpose invalidates the cache again Connect the docs server before the first prompt of the task, or leave it connected; editing MCP config only takes effect on restart anyway tools_changed stops increasing
Three misses from expiry ttl_expired_5m: 3 The test runs leave gaps longer than five minutes, so the next turn finds a cold prefix Set promptCacheTtl to 1h for the main conversation The line shows 1h TTL; no new ttl_expired_5m
Size of the next cold turn recache_tokens_if_cold: 124000 The whole conversation would be rewritten Compact at the end of the task, not after a break The figure drops after /compact

After the two changes, a comparable session the next day reads (Illustrative):

Prompt cache (main):   41 requests · 93% of input tokens from cache · 1 miss (last 1h 12m ago, 96.4k tokens re-cached) · warm (1h TTL, last activity 1m 05s ago)

The one remaining miss came after a lunch break longer than an hour, which no TTL setting will cover. The trade-off to keep in view is that every cache write in that session, including each turn’s small appended write, now costs 2 times base input instead of 1.25 times. The decision table further down shows when that premium pays for itself.

Show cache state in your status line

/usage is something you open; a status line is always on screen. Claude Code passes JSON session data on stdin to a script you configure and displays whatever it prints, and since v2.1.251 that JSON includes the prompt_cache object (status line documentation). The script runs locally and consumes no API tokens. If you already have a status line from the status line setup guide, add the cache fragment to it rather than replacing it.

  1. Create the file ~/.claude/statusline.sh with these contents:

    #!/bin/bash
    # Prompt cache state for the main conversation (fields documented for v2.1.251+)
    input=$(cat)
    
    MODEL=$(echo "$input" | jq -r '.model.display_name')
    # Read booleans without "//": jq treats false like null there
    OBSERVED=$(echo "$input" | jq -r '.prompt_cache.caching_observed')
    
    if [ "$OBSERVED" = "null" ]; then
      echo "[$MODEL] cache: waiting for first response"
      exit 0
    fi
    if [ "$OBSERVED" = "false" ]; then
      echo "[$MODEL] cache: none reported"
      exit 0
    fi
    
    WARM=$(echo "$input" | jq -r '.prompt_cache.warm')
    TTL=$(echo "$input" | jq -r '.prompt_cache.ttl // "?"')
    HIT=$(echo "$input" | jq -r '(.prompt_cache.hit_ratio // 0) * 100 | floor')
    MISSES=$(echo "$input" | jq -r '.prompt_cache.misses // 0')
    EXPIRES=$(echo "$input" | jq -r '.prompt_cache.expires_at // empty')
    CAUSE=$(echo "$input" | jq -r '(.prompt_cache.last_miss_cause.causes // []) | join(",")')
    
    STATE="cold"
    if [ "$WARM" = "true" ] && [ -n "$EXPIRES" ]; then
      LEFT=$(( (EXPIRES - $(date +%s)) / 60 ))
      STATE="warm ${LEFT}m left"
    fi
    
    LINE="[$MODEL] cache ${HIT}% · ${MISSES} misses · ${TTL} ${STATE}"
    [ -n "$CAUSE" ] && LINE="$LINE · last: $CAUSE"
    echo "$LINE"
    
  2. Make it executable with chmod +x ~/.claude/statusline.sh.

  3. Point your user settings at it in ~/.claude/settings.json:

    { "statusLine": { "type": "command", "command": "~/.claude/statusline.sh" } }
    
  4. Feed it mock input before relying on it, as the status line docs recommend:

    EXP=$(( $(date +%s) + 1800 ))
    printf '{"model":{"display_name":"Sonnet"},"prompt_cache":{"caching_observed":true,"warm":true,"ttl":"1h","expires_at":%s,"hit_ratio":0.91,"misses":2,"last_miss_cause":{"causes":["tools_changed"],"tools_added":2,"tools_removed":0}}}\n' "$EXP" | ~/.claude/statusline.sh
    
  5. Check the printed line. With that input the script’s logic should print [Sonnet] cache 91% · 2 misses · 1h warm 30m left · last: tools_changed, or 29m left if a second ticks over. That is the expected result of the script, not captured output.

  6. Start a real session and send a prompt. Before the first response the line reads cache: waiting for first response, because the prompt_cache object is absent until then.

The script is shaped around how the fields behave. caching_observed false means caching is off or your provider or gateway does not report it, which is a different problem from a cold cache, so it gets its own message. warm is false whenever the last response reported no cache tokens, even when caching was observed earlier, and expires_at is null in that case, so the minutes-left figure only appears for a warm prefix. You do not need a timer to see the cache go cold: Claude Code re-runs the status line when a warm prompt cache in the last data it sent reaches its expires_at time. last_miss_cause is null until the first miss and whenever no cause was identified, which the // [] fallback handles.

The documented cause names are tools_changed, system_prompt_changed, ttl_expired_5m and likely_server_side. With tools_changed you also get tools_added and tools_removed; with system_prompt_changed you get system_char_delta, the change in system prompt length in characters. If the line stays blank, the status line docs list the usual culprits: a script that is not executable, output written to stderr instead of stdout, a workspace you have not trusted, or disableAllHooks.

Confirm the TTL with claude -p JSON

The status line tells you the TTL of the current prefix. To check which TTL a fresh request actually wrote, Anthropic’s prompt caching page gives a one-line test. If you script Claude Code already, the headless mode guide covers -p in depth; here you only need the usage block.

  1. Change into the project you care about, because the cache is scoped to machine and directory.

  2. Run the documented check from that directory:

    claude -p "hello" --output-format json | jq '{write: .usage.cache_creation, read: .usage.cache_read_input_tokens}'
    
  3. Read usage.cache_creation. Claude Code reports one-hour writes under ephemeral_1h_input_tokens and five-minute writes under ephemeral_5m_input_tokens. On a subscription within plan usage with no overrides, an Illustrative result looks like this:

    {
      "write": { "ephemeral_1h_input_tokens": 18342, "ephemeral_5m_input_tokens": 0 },
      "read": 0
    }
    
  4. Interpret which field is nonzero, not the size. Counts can be small on a second run, because a sequential session in the same directory can read the prefix the first run cached when the git snapshot matches.

  5. To prove an override works, run the same command with the opposite setting, for example FORCE_PROMPT_CACHING_5M=1 claude -p "hello" --output-format json | jq '.usage.cache_creation', and confirm the write moves to ephemeral_5m_input_tokens.

  6. If both write fields and the read count are zero, nothing was cached at all. Treat that as the “no prompt caching reported” case in the troubleshooting table, not as a TTL problem.

claude -p runs land in the main-conversation bucket, so this test checks promptCacheTtl. It does not tell you what your subagents received.

Choose a TTL for each bucket

Claude Code picks a TTL per request, and every request falls into one of two fixed buckets. The main conversation covers your interactive turns, -p runs, Agent SDK turns and the helpers Claude Code runs inline with them. Everything else covers subagents, workflows, in-process teammates, forks, compaction and session titles. The defaults from the prompt caching page:

Request 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, which get one hour Five minutes

Once a subscriber goes past the plan’s limit and Claude Code draws on usage credits, the main conversation drops to five minutes, because that usage is now billed and five-minute writes are cheaper. If you run on credits regularly, the usage credits guide covers caps and switching them off; the TTL choice sits alongside it.

The decision, filled in for common cases:

Your situation Main conversation Everything else Reasoning
Subscription, within plan usage Leave the default (1h) Leave the default (5m) You already get the hour where it matters
Subscription on usage credits, with breaks of 10 to 60 minutes promptCacheTtl: "1h" 5m Credits are billed; one cold rewrite after a break costs more than the write premium
Subscription on usage credits, continuous short bursts Default 5m 5m The longer lifetime would go unused while every write costs more
API key or cloud provider, meetings and long test runs between turns promptCacheTtl: "1h" 5m Same arithmetic as the credits case
API key, tight pairing loop with replies inside a few minutes Default 5m 5m Each turn refreshes the timer anyway
Many short subagents that finish within minutes No change 5m Unless Claude resumes that subagent soon after, nothing reads its cache again, so a 1h write is waste
Long subagents or workflows that sit idle more than five minutes between their own turns No change subagentPromptCacheTtl: "1h" Their own prefix goes cold between turns at 5m
Behind an LLM gateway or on Bedrock 1h only after confirming support 5m A gateway must forward the anthropic-beta header; Bedrock support varies by model

For a per-token setup the break-even is easy to work out. Take an Illustrative day on Sonnet 5.5 with an API key: the session’s first write covers a 100,000-token prefix, and 60 turns each append about 3,000 tokens, so 280,000 tokens are written over the day. At the five-minute rate that is $0.70; at the one-hour rate it is $1.12, a premium of $0.42. Now add four breaks of 15 to 40 minutes. Under the five-minute TTL each break forces a rewrite of roughly 100,000 tokens for $0.25, where a read would have cost $0.01, so the four breaks add $0.96. The hour comes out about $0.54 ahead. With no breaks at all, the five-minute default wins by the same $0.42, and two breaks a day are enough to tip it, since each saves $0.24. The model ignores that the prefix grows during the day, which only strengthens the case for the hour when breaks happen late. The pricing page states the general rule at the standard 0.1x read rate: a five-minute write pays off after one cache read, a one-hour write after two.

Apply the choice:

  1. Add the keys to ~/.claude/settings.json, or to project settings if the whole team should share them. Each takes 5m or 1h, and Claude Code ignores any other value:

    {
      "promptCacheTtl": "1h",
      "subagentPromptCacheTtl": "5m",
      "statusLine": { "type": "command", "command": "~/.claude/statusline.sh" }
    }
    
  2. Start a fresh session so the new value is in force, then repeat the claude -p check and look for ephemeral_1h_input_tokens above zero.

  3. Watch the status line during your next break: it should still read 1h warm when you come back within the hour.

When more than one control is set, Claude Code takes the first match in this order:

  1. FORCE_PROMPT_CACHING_5M=1, which forces five minutes for both buckets.
  2. The bucket’s environment variable: CLAUDE_CODE_PROMPT_CACHE_TTL or CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL.
  3. The bucket’s setting: promptCacheTtl or subagentPromptCacheTtl.
  4. For a subagent’s requests, the cacheTtl value in the subagent’s experimental frontmatter field (v2.1.248 or later), where a 1h is ignored while a subscription is using usage credits.
  5. ENABLE_PROMPT_CACHING_1H=1, which requests one hour for both buckets.
  6. The bucket’s default.

That order explains most “my setting is ignored” reports: an environment variable exported in a shell profile beats the settings file, and FORCE_PROMPT_CACHING_5M beats everything. The environment variables guide shows how to prove which values a session actually sees. Organisations can put any of these variables in the env block of managed settings, and Anthropic suggests FORCE_PROMPT_CACHING_5M=1 when you need to override a longer TTL set there for debugging. The Agent SDK cost-tracking page adds that ENABLE_PROMPT_CACHING_1H asks for one hour on every request in both buckets, that the per-bucket controls take precedence over it, and that promptCacheTtl: "1h" keeps the one-hour cache on the main conversation while you draw on usage credits.

Habits that break the prefix, and ones that don’t

Most misses come from a handful of actions that feel free. Each causes one slow, more expensive turn, after which the new prefix is cached again. This table is limited to behaviors in Anthropic’s documentation:

Action Effect on the cache What you will see Do this instead
/model to another model Separate cache per model; the next request reads the whole history uncached One slow turn and a miss Pick the model at the start; Claude Code asks you to confirm while the cache is warm
Plan mode with the opusplan model setting Each plan-mode toggle switches between Opus and Sonnet, so it is a model switch A fresh cache on each toggle Use one fixed model for long tasks
Changing effort level Rebuild on most models; kept on Opus 5.5, Sonnet 5.5, Haiku 5.5 and Fable 5.1 with an API key or subscription A confirmation prompt while warm on models that rebuild Set effort before the task
Turning fast mode on or off Fast and standard speed keep separate caches The first fast turn re-reads the conversation at fast mode rates Turn it on at session start
Connecting an MCP server mid-session No effect when tool search defers tools, the default on supported models; invalidates when tools load upfront tools_changed with tools_added Connect servers before the first prompt
Denying a whole tool such as Bash Kept with tool search active; invalidates when tool search is off or unavailable tools_changed with tools_removed Scoped rules such as Bash(rm *) never change the tool list
Enabling a plugin Skills, commands, agents, hooks, monitors and themes append and keep the cache; plugin MCP servers follow the MCP rules /reload-plugins warns instead of applying a full re-read Use /reload-plugins --force only when you accept the rebuild
/compact or auto-compaction Replaces history with a summary; the system prompt and project context can stay cached An expected rebuild, not a miss Compact at a natural break; use /rewind to abandon a path
Sharing many images Claude Code removes a batch of the oldest images and PDFs when request limits are reached One slow turn per batch Share fewer, smaller screenshots
Idling past the TTL The whole prefix expires Cold, then ttl_expired_5m on a five-minute TTL Use the 1h TTL if you pay per token and break often

Several actions look risky but keep the prefix, because they append to the end or never touch the request: editing files in your repository (Claude Code appends a notice that the file changed), editing CLAUDE.md mid-session (the edit does not apply until /clear, /compact or a restart), changing permission mode, switching output style, invoking skills and commands (unless their frontmatter names another model), /recap, /rewind and spawning a subagent. A subagent builds its own cache and leaves the parent’s untouched; a fork inherits the parent’s system prompt, tools and history exactly, so its first request reads the parent’s cache.

Compaction deserves one more note. While the cache is warm, the summarization request reads your prefix from cache, so a mid-session /compact costs a fraction of what the context size suggests. After a break longer than the TTL it reprocesses the whole history uncached, which is why compacting an old resumed session is the expensive case. On the one-hour TTL Claude Code can compact a long idle conversation by itself; set idleCompaction to false (v2.1.290 or later) if you would rather choose the moment.

Troubleshooting a cache that won’t stay warm

Symptom Likely cause Fix Expected observation
Hit share stays low and misses climb every few turns Tool definitions changing while tools load upfront Connect MCP servers before the first prompt; avoid mid-task deny rules for whole tools miss_causes.tools_changed stops rising
A miss with system_prompt_changed The system prompt changed under the session; system_char_delta shows by how much. One documented case is a resumed conversation that kept an outdated prompt switching to the current one at its first compaction Upgrade and change system prompt flags between tasks, not mid-task One miss, then a rising hit share
Cold after a coffee break Five-minute TTL on an API key, cloud provider or usage credits promptCacheTtl: "1h" if breaks are frequent 1h TTL on the line; still warm after a 20-minute break
1h setting has no effect Value not exactly 1h; version older than v2.1.242; FORCE_PROMPT_CACHING_5M or CLAUDE_CODE_PROMPT_CACHE_TTL set in the shell or a managed env block Fix the value, upgrade, or unset the override claude -p shows ephemeral_1h_input_tokens above zero
no prompt caching reported by the API or caching_observed: false A DISABLE_PROMPT_CACHING variable is set; a gateway removes the cache_control markers; a Bedrock model or region without cache support Unset the variable; make the gateway forward markers; check Bedrock’s supported models Nonzero cache read and write counts
Gateway user stuck at five minutes Part of the one-hour request travels in the anthropic-beta header Forward that header unchanged; for a Claude apps gateway, ask the admin for a gateway policy ephemeral_1h_input_tokens above zero
Usage high but the main line looks healthy Subagents, workflows and teammates are not in the main line Check the plan attribution breakdown, or modelUsage in the Agent SDK Subagent share visible; set subagentPromptCacheTtl only if they idle over five minutes
Last cause is likely_server_side Nothing you changed locally explains the miss Note it; do not change settings in response Misses do not recur
/compact on an old session is slow and costly The summarization request reads history uncached after the TTL Compact before stepping away, or /rewind instead The next compaction runs on a warm cache

Gateways deserve their own check, because the worst documented failure is silent. A gateway that forwards the cache_control markers unchanged caches like the provider’s own endpoint. One that rejects them with a 400 error naming cache_control triggers a fallback in Claude Code that moves the marker onto your last conversation message, so only the appended system-context block bills uncached. One that removes the markers and still returns success bills your entire conversation history as uncached input on every turn, and a gateway that converts block-form system content to a plain string drops the marker the same way. If you route through a translator or proxy, record its cache behavior in the bridge register next to everything else that sits between your CLI and the model.

Roll back to the defaults

If a change made things worse, or you are returning a machine to standard behavior, undo it in this order:

  1. Delete promptCacheTtl and subagentPromptCacheTtl from ~/.claude/settings.json and from any project settings file you edited.
  2. Remove CLAUDE_CODE_PROMPT_CACHE_TTL, CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL, ENABLE_PROMPT_CACHING_1H and FORCE_PROMPT_CACHING_5M from your shell profile and from any settings env block, then open a new terminal.
  3. Start a fresh session and rerun the claude -p check. On a subscription within plan usage, expect ephemeral_1h_input_tokens; on an API key, cloud provider or usage credits, expect ephemeral_5m_input_tokens.
  4. If a value persists after that, it is probably coming from managed settings; check with whoever administers them before working around it.
  5. Use DISABLE_PROMPT_CACHING=1 only as a short diagnostic when you suspect a provider- or model-specific caching problem, and unset it as soon as you have your answer. Anthropic’s own advice is to leave caching enabled for normal use.
  6. Keep or remove the status line as you like; it costs no API tokens.

Success looks like this: the hit share climbs and stays high through the session, the same cause stops reappearing in miss_causes, the JSON confirms the TTL you intended, and the status line reads warm while you are working. Failure looks like paying one-hour write rates on a lane that never comes back within five minutes, or a setting that does not move the JSON fields at all. Both are visible within one session, which makes the cache one of the easier parts of running agents as an operating discipline to measure rather than argue about.

Sources

  1. Ethan Lin (@EthanLinAI) on X, Oct 31, 2026: Is your Claude Code session actually hitting the prompt cache?
  2. Anthropic, Claude Code Docs: How Claude Code uses prompt caching
  3. Anthropic, Claude Code Docs: Manage costs effectively
  4. Anthropic, Claude Code Docs: Customize your status line
  5. Anthropic, Claude Platform Docs: Pricing
  6. Anthropic, Claude Code Docs: Track cost and usage (Agent SDK)
YOU'RE THROUGH THIS ONE.

Keep connecting the dots.

Back to the library

Many assistants.
One place to remember.

Bring your AI sessions, context, and usage together with Automater.

Meet Automater