# Why your prompt cache never hits, and what it saves

> A prompt cache pays only when the start of every request is identical and calls come often enough. The usual reasons it misses, and a way to check.

Published 2026-10-28 · https://aiskills402.com/blog/prompt-caching-misses

A prompt cache lets the API remember the beginning of a request, so the next request that starts with exactly the same text does not pay full price to process it again. When it works, repeated calls get cheaper and start answering sooner. When it does not, you pay for the marks you added and get nothing back, and no error tells you so. The only sign is a usage counter that stays at zero. This guide explains what has to be true for a hit, the five ordinary reasons for a miss, and a short routine for finding out which one applies to you.

## What a hit needs

The cached part of a request is built in a fixed order: tool definitions first, then the system prompt, then the messages. You tell the service where the reusable part ends in one of two ways: a `cache_control` field on a single block, or one such field set once for the whole request, which lets the service choose the final block that can be cached. Either way, the text from the first character through that point is stored as one prefix, and a later request reads it only when the entire prefix matches ([Claude Platform docs](https://platform.claude.com/docs/en/build-with-claude/prompt-caching)).

So there are three conditions, and a miss is always a failure of one of them. The text before the mark must not change between calls. The prefix must be long enough for the model to accept it for caching. And the next call must arrive while the stored entry is still alive.

## What it saves

Cache writes cost more than ordinary input, and reads cost much less. The vendor's pricing table lists the exact multipliers for each lifetime and model; they change, so look them up there rather than trusting a figure in an article ([Claude Platform docs](https://platform.claude.com/docs/en/build-with-claude/prompt-caching)). The arithmetic is simple. A large prompt that is reused many times within the lifetime is far cheaper with the cache. A prompt that is written and never read costs a bit more than having no cache at all. That is why a silent miss matters: the first number goes up and the second never appears.

Time is the other saving. Reading a stored prefix skips the work of processing it, so the first token tends to arrive sooner. We did not measure that, and the size of the gain depends on the prompt.

## How to see a miss

Every response carries a usage block. Three fields matter: the tokens written to the cache, the tokens read from it, and the tokens after the last mark, which are plain input. If both the written and the read counters are zero, nothing was stored, and the documentation points first at a prefix that is below the minimum length ([Claude Platform docs](https://platform.claude.com/docs/en/build-with-claude/prompt-caching)). If the written counter is large on every call and the read counter stays at zero, something before the mark is changing, or the calls are too far apart. Log these three counters for every call. Without them you are guessing.

## Reason one: a value that changes sits before the mark

This one turns up more than any other. A system prompt that opens with the current time, a request identifier, a session label or the user's name is different on every call, so the stored prefix never matches. The mark may be perfectly placed and the prefix may be huge, and it still misses every time.

The remedy is to move anything that varies below the mark. Keep the long, fixed instructions first, mark the end of them, and put the clock, the identifier and the user's question after that. Whatever follows your final mark is billed as normal input and may differ freely. The same thinking applies to the whole-request setting: the mark follows the final block, so if that block is rewritten on every call, the entry is new every time. For a growing chat, the last block is normally the newest user turn, which is fine, because the earlier turns stay unchanged and the next call finds them. This holds as long as each turn adds only a modest number of blocks, because the lookup checks a limited window behind the mark; the documentation describes that limit and the option of several marks.

## Reason two: no mark where a big prefix repeats

The opposite mistake is to have a long, stable prefix and no mark at all. Nothing is cached unless you ask. If the same instructions, examples or reference text are sent again and again, put a mark right behind that block, or use the whole-request setting if your conversation grows turn by turn. A request that is sent only once gains nothing from a mark, so this reason applies only to repeated traffic.

## Reason three: the prefix is too short

Each model has a minimum prefix length, and a shorter prefix is processed normally with no error. The documented minimums range from 512 tokens on some models to 4,096 on others, so the same prompt can be cached on one model and ignored on another ([Claude Platform docs](https://platform.claude.com/docs/en/build-with-claude/prompt-caching)). This bites when you switch models to save money, or when you cut a system prompt down. Check the current table for your exact model rather than relying on memory, because it changes with each release. If your prefix falls just short, the documentation says padding it with genuinely useful material up to the limit can pay off, because reads cost so little compared with plain input.

## Reason four: the definitions are edited between calls

The order tools, system, messages works as a chain, and a change invalidates its own level and everything after it. Altering a tool definition, whether its name, its description or its parameters, throws away the stored tools, system and messages. Altering the system text throws away the system and messages. Touching just the messages leaves the tools and the system intact ([Claude Platform docs](https://platform.claude.com/docs/en/build-with-claude/prompt-caching)).

In practice this appears when an application adds or removes a tool depending on the step, reorders a tool list that is built from a set with no stable order, or appends a note to the system prompt as the task proceeds. Each of those looks harmless in a code review. Keep the tool list and the system text fixed for the lifetime of a task, and put changing guidance into a message after the mark.

## Reason five: the calls are too far apart

An entry lives for 5 minutes by default, and every read renews that time for free. The countdown begins when the writing or reading request starts, so a slow, long answer uses up part of the window before the reply is even finished ([Claude Platform docs](https://platform.claude.com/docs/en/build-with-claude/prompt-caching)). A job that fires every ten minutes will write a fresh entry every time and never read one. Your options are to call more often, which is rarely sensible, to ask for the one-hour lifetime and accept the higher write price, or to accept that this workload does not benefit.

One related trap is parallel calls. An entry becomes available only after the first response begins, so a burst of simultaneous requests with the same prefix will each write their own copy. Send one, wait for it to start answering, then send the rest ([Claude Platform docs](https://platform.claude.com/docs/en/build-with-claude/prompt-caching)).

## A routine that finds the cause

1. Send the same request twice, a few seconds apart, and read the three usage counters on each response.
2. If the second call shows reads, the cache works for this request; look at your real traffic pattern next.
3. If both counters are zero on both calls, check the prefix length against the minimum for your model.
4. If writes appear twice and reads never do, print the exact text before your mark for the two calls and compare them character by character. A clock, an identifier or a reordered tool is usually visible at once.
5. If a quick back-to-back test hits but production does not, measure the real gaps between production calls and set them beside the entry lifetime.

The back-to-back test is the one that can fail: if it passes only because you reused one fixed string by hand, vary the inputs the way production does before you trust it.

## When this does not apply

What is written here describes the Claude API as documented on the day we read it, and the details move: model minimums, prices and the number of marks allowed are all changed by new releases, so confirm them on the vendor's page. It does not cover other providers, whose caching works differently, and it says nothing about caches you build yourself in front of an API. Prompts that are tiny, sent once, or sent rarely gain little from any of this, and a mark there is just extra cost. No new experiments were run here; the text paraphrases public documentation and common patterns, so its use is to show where to look, not to predict your savings. Your own usage counters are the only real measure.

Resending a long history is the same cost question seen from the side of a coding session, and our note on [clearing or compacting a long Claude Code run](/blog/claude-code-clear-or-compact) weighs keeping context against paying for it.

## Reviewing a request before you ship it

To have one request checked before it ships, our [prompt-caching-review](https://aiskills402.com/skills/prompt-caching-review) skill reads a description of a Claude API request, with the model, the sizes, the marks and how often it is sent, and reports only the problems above, one line each, naming a code, the place and the fix. When the request is sound it answers with one short sentence saying nothing was found. Harmless look-alikes, like a time value kept below the final mark, are left out.
