Артыкул апублікаваны на англійскай мове.
Prompt Cache Misses in Claude Code: Why an 86% Hit Ratio Hides the Cost
A priced model of one iOS-monorepo session shows seven prompt-cache misses outweighing 33 hits, and how to stabilize tools, skills and CLAUDE.md as cache keys.
A hit ratio of 86% sounds healthy. Yet in a modeled afternoon of Claude Code work, seven cache misses cost more than the other 33 turns put together. The picture changes once you price each miss and trace it to a cause.
Version 2.1.260 of Claude Code put a new line in /cost: a best guess at why your latest prompt-cache miss happened. The candidates are changed tool definitions, a changed system prompt, or a pause longer than the TTL. Then 2.1.261 brought /skill-doctor, which reports skills that are loaded but never used and what they cost in context. That release also fixed a bug in which agent-team teammates repeated their first-turn tool announcements on the second turn, altering the request prefix and missing the cache.
Read together, those entries say the vendor has begun treating context assembly as cache-key engineering, and most users haven't. A prompt cache is a prefix cache. Everything placed ahead of the conversation, in the order you placed it, is the key. To see what that costs on an ordinary day, the numbers below come from a model of one.
How the cache keys requests
Anthropic's caching documentation spells out the structure. Requests are layered as tools, then system, then messages, and any change at one layer wipes that layer and every layer after it. Renaming a tool or touching its schema throws out the whole cache; editing the system prompt keeps the tools entry and loses the rest. With each request, the API hashes the prefix up to your breakpoint, and if nothing matches it steps backward block by block, looking at no more than 20 positions. You can set up to four breakpoints, and in this fixture they follow the tool array, follow the system prompt and sit on the final message.
By default an entry lives five minutes, and every read renews it for free. Writing an hour-long entry costs double the base input price. Claude Fable 5.1 charges $10 per million input tokens, $12.50 per million to write a five-minute entry, $20 for an hour entry and $0.25 per million to read. Reading from the cache therefore costs 2.5% of sending the same tokens uncached, and a new five-minute write costs 50 times a read. Hence a miss isn't a small slowdown; whatever dropped out of the cache is suddenly billed 50× higher.
One afternoon, seven ordinary events
The Swift package ContextCacheKit arranges a request the way the API hashes it, simulates a cache using the documented TTL and lookback rules, and puts a price on each turn. Its fixture imitates a realistic iOS-monorepo session. The tool set holds 16 harness tools and 17 MCP tools served by Xcode, GitHub and Linear (19,140 tokens). The rest is a harness preamble, a CLAUDE.md of 200 lines, a memory file, and a pair of skills that load at startup (17,700 tokens). Every turn starts with that shared 36,840-token prefix, and 40 user turns follow, 45 seconds apart.
Seven events then occur, the sort that happen on real teams. On turn two a teammate rewrites the first message. Turn eight loads a networking skill, spliced into the system prompt. On turn 14 the GitHub MCP server reconnects, registering its eight tools again at the tail of the list. A 22-minute lunch falls before turn 20. Turn 26 brings a merged CLAUDE.md change, turn 33 a newly loaded testing skill, and on turn 37 a reconnecting client re-renders the Bash tool definition, which grows from 1,450 to 1,480 tokens.
Overall the session hits 85.6% of the time, a number that wouldn't make anyone look twice on a dashboard. The ledger tells another story. Input cost $4.65. Perfect reuse of the previous prefix on every turn would have cost $1.53, and no cache at all $23.10. Seven misses account for $3.41 of the bill, and the remaining 33 turns for $1.24 together.
Sorted by dollars above ideal, the biggest offender is the tool schema re-render at $0.91. Three separate system-prompt changes follow with $0.88 combined, then the lunch break at $0.69, the tool reorder at $0.64 and, far behind, the teammate's rewrite at $0.002.
Two items in that ranking changed how the cause of cost looks. Thirty extra tokens on the Bash schema made the system resend all 75,249 tokens of the request, since tools head the hierarchy. Merely reordering the GitHub tools, with the schema untouched byte for byte, did the same to 52,648 tokens. Meanwhile the bug that earned a changelog entry, the teammate rewriting message zero, turned out to be the cheapest miss, re-caching only 157 tokens because messages are at the bottom. The harness repaired the miss it could see, while the expensive ones come from your own configuration.
The diagnosis logic is simple: the first thing in hash order that would break the prefix wins.
// Abridged from MissDiagnoser.swift: the first thing in hash order that would break the prefix wins.
public static func diagnose(previous: ContextSnapshot?, current: ContextSnapshot,
elapsed: TimeInterval, ttl: CacheTTL) -> MissCause {
guard let previous else { return .none } // cold start, not a miss
if toolsDiffer(previous, current) { return .toolSetChanged(...) }
if previous.tools.map(\.name) != current.tools.map(\.name) { return .toolOrderChanged }
if previous.system != current.system { return .systemSegmentChanged(segments: changed) }
if let i = firstRewrittenMessage(previous, current) { return .messagePrefixRewritten(atIndex: i) }
return elapsed > ttl.seconds ? .idlePastTTL(idleSeconds: elapsed) : .none
}
Treat the prefix as a cache key
Replay the afternoon with an engineered prefix, the stabilized fixture. Tool order is fixed and schemas stay frozen for the whole session. Skills enter the conversation as system-role messages and not as text spliced into the top-level system prompt, which per the docs on Fable 5.1 leaves the system and message caches intact. The CLAUDE.md merge is held until a session boundary. Lunch stays as it is, because a TTL is a policy decision and not a prefix defect.
That run has a single miss, the lunch break. Hit ratio: 94.2%. Cost: $2.22, or $0.69 above ideal. All the fixes changed what the harness sends; none touched model behavior.
The remaining lever is the TTL. Move the stabilized run to one-hour entries and no misses remain, at a cost of $2.11. That saves only 11 cents against the five-minute version, since each fresh token now costs $20 to write rather than $12.50 and an afternoon of that surcharge adds up to about one lunch break. The longer TTL pays only on days that have gaps, so set it per team and not globally.
The skill placement is the part that moves the most money. Same skill, same 5,200 tokens, and only where it goes differs:
// Same skill, same 5,200 tokens. Only the placement changes.
.loadSkill(name: "skill:networking-layer", tokens: 5_200, placement: .systemPrompt)
// → system prompt changed: re-sends the transcript after it
.loadSkill(name: "skill:networking-layer", tokens: 5_200, placement: .appendedMessage)
// → hit: costs only its own 5,200 tokens, once
What to track
Plan the shared prefix like app launch time. The tools and skills your monorepo loads by default add up to 36,840 tokens that get hashed every turn, and a hit ratio averages a whole session, hiding which turn burned the money. The figure worth putting on a dashboard is dollars over ideal, by cause, by day. /cost now supplies the cause, and the ledger the dollars.
A hit ratio is an average, but a cache miss is an event, so budget the events.
The fixture is a model and not telemetry, and its constants are assumptions, so with your own tool list and prefix sizes the ranking will move. The overall shape won't. Causes at the top of the hierarchy cost most, and no changelog will repair them for you. The simulator, fixtures, 30 tests and a SwiftUI ledger view are in the project's demo repository, whose Demo target hadn't been run on a Simulator at publication.