← Claude Code Hub
✦ Tip #199 Sep 16, 2026

prompt_cache in Claude Code: know if your cache went cold before you type

You step away for coffee, and the first message you type when you get back reprocesses the whole conversation. Your status line can warn you first, at the exact second the cache dies.

A timeline: typing with the bar green at 98 percent, you step away, and at expires_at the bar repaints amber warning about 461k tokens to type

TL;DR The JSON your status line receives carries a prompt_cache object with warm, ttl, expires_at and recache_tokens_if_cold. A dozen lines of jq and the bar repaints itself the second your cache dies, telling you how many tokens you re-cache if you type.

You step away for coffee. You come back, type the first thing on your mind, and that turn reprocesses the whole conversation at full price. Nothing warned you, and you paid for it without ever knowing you did.

The /cost line tells you afterwards, and only if you open the screen. Your status line can tell you before, and one line in the docs explains why: Claude Code re-runs your script when a warm cache reaches its expires_at. It knows when the cache is going to die, and it wakes your bar at that exact moment.

What rides in the JSON

This is what my own status line received a little while ago, untouched:

"prompt_cache": {
  "warm": true,
  "caching_observed": true,
  "ttl": "1h",
  "expires_at": 1789595743,
  "requests": 238,
  "misses": 1,
  "expected_rebuilds": 1,
  "hit_ratio": 0.9838970255083798,
  "cache_write_tokens": 1207215,
  "miss_recache_tokens": 340839,
  "last_miss_at": 1789590652,
  "last_miss_cause": { "causes": ["ttl_expired_1h"] },
  "miss_causes": { "ttl_expired_1h": 1 },
  "recache_tokens_if_cold": 461120
}

Look at the miss. One in 238 requests, and its cause is ttl_expired_1h: exactly what this tip is about, the cache expiring while you are not looking. It happened to me twenty-five minutes before I captured this and I never noticed, because my bar was not painting it yet.

Then look at the last field, the one you cannot get anywhere else. recache_tokens_if_cold is 461,120 tokens: what my next message reprocesses if the cache has gone cold by then. That is not a metric, it is the price of typing.

The segment

It goes inside the script you already have. If you do not have one yet, set the status line up first and come back.

if [ "$(echo "$input" | jq '.prompt_cache != null')" = "true" ]; then
  WARM=$(echo "$input" | jq -r '.prompt_cache.warm')
  PCT=$(echo "$input" | jq -r '(.prompt_cache.hit_ratio // 0) * 100 | floor')
  TTL=$(echo "$input" | jq -r '.prompt_cache.ttl // "?"')
  COLD=$(echo "$input" | jq -r '(.prompt_cache.recache_tokens_if_cold // 0) / 1000 | floor')

  if [ "$WARM" = "true" ]; then
    CACHE="${GREEN}●${RST} ${DIM}cache${RST} ${PCT}% ${DIM}${TTL}${RST}"
  else
    CACHE="${YELLOW}▲ cache cold · +${COLD}k to type${RST}"
  fi
fi

Two states, and this is what it prints against the JSON above:

● cache 98% 1h
▲ cache cold · +461k to type

That first check is not optional. The object does not exist until the first API response: I verified it by capturing the JSON of a freshly opened session, where the prompt_cache key is simply not there. Without the guard, the segment paints amber with a +0k before you have typed a single word.

It is not a countdown, and that is the good part

The status line has no clock: it runs on events. These ones:

  • a new assistant message arrives
  • a /compact finishes
  • you change permission mode or vim mode
  • you change the command in your settings
  • a rate-limit window reaches its resets_at
  • a warm cache reaches its expires_at

That last one does the work. While you are typing the bar sits green because it repaints on every response. You walk away, and the second the cache expires Claude Code runs your script one more time and the bar turns amber on its own, with the session idle and you doing nothing. You come back, glance down, and you already know whether typing is expensive.

If you genuinely want the countdown, that is a different thing and you ask for it: add refreshInterval to your statusLine and the script also runs every N seconds, so you can render the minutes left on expires_at.

{
  "statusLine": {
    "type": "command",
    "command": "~/.claude/statusline.sh",
    "refreshInterval": 30
  }
}

The minimum is 1 second. And do not worry about the spend: the status line runs locally and consumes no API tokens, the docs say so themselves.

Reference: the fields you will actually use

Field What it is What you render it for
warm Whether the cached prefix is still inside its TTL The traffic light
ttl "5m" or "1h" Knowing how much room you have
expires_at When it goes cold, in epoch seconds The countdown, with refreshInterval
recache_tokens_if_cold What your next message re-caches if it is already cold The price of typing
hit_ratio Cache reads over total input Session health
misses · miss_causes Misses and their cause, by name Spotting the same culprit twice

All thirteen fields and their small print live in the docs. These six rows are the ones that fit in a bar.

That ttl is worth a look before you build anything, because not everyone gets the full hour: the main conversation gets sixty minutes while you are inside your plan's included usage, and five as soon as you move to usage credits or run on an API key. At five, any interruption brings you back cold, which is exactly why compacting before you step away beats compacting when you get back.

With this the bar tells you three things at a glance: whether the cache is alive, whether your session is making use of it, and what the next message costs. For the rest of the mechanism, what exactly gets cached and what breaks it, and if you would rather pull the number on demand than keep it pinned, the /cost line. That same JSON also carries how much of your limit is gone, the other neighbour worth a slot in the bar.

Official docs: Status line: prompt cache fields

Requirements

  • Claude Code v2.1.251 or later for the prompt_cache object. last_miss_cause and miss_causes need v2.1.260.
  • jq for the snippet. The JSON above is a real capture from my session on v2.1.270, and both rendered states were run against that same JSON.
  • The object covers the main conversation only. What your subagents spend does not count.
Free guide

The 51 essentials, as a guide.

One page per tip. Five chapters. What I actually use daily in production. No theory, no fluff.

  • I. Getting started 10 tips
  • II. Awareness 3 tips
  • III. Mastery 22 tips
  • IV. Autonomy 10 tips
  • V. Comparison 6 tips
Are you a professional Web developer?

You'll receive the guide by email · You join the Gravitas newsletter · Unsubscribe anytime

of 51
#

Wmedia · 51 Tips
Free guide · 51 tips · 5 chapters

The 51 essentials, as a guide.

Are you a professional Web developer? · Unsubscribe anytime
Workshop for teams

Multiply your team's output without sacrificing quality: a 6 to 8 hour AI First workshop, online, on the Claude platform.

See the workshop

Want the 51 Claude Code essentials as a guide?