cache_read_input_tokens is 0 with no error
The confusing part
Caching is configured, the request succeeds, and cache_read_input_tokens comes back as zero
anyway — on every single request, not intermittently. There's no error field, no warning, nothing
in the response that says caching didn't happen. The bill simply reflects a fully-priced request
as if caching were never turned on, and the only way to notice is actively checking the usage
block against what you expected rather than waiting for something to flag it.
A checklist, not a single cause
This symptom has several distinct root causes, and they're worth checking in order rather than guessing:
- Something in the prefix changes on every request — a timestamp, a request id, a session id, or any other value interpolated near the start of the prompt. If the bytes differ every time, there's never a prior cache entry to read, by definition.
- Non-deterministic serialisation of structured data — dumping a dictionary or iterating a set without a stable key order produces different byte output run to run even when the underlying data hasn't changed.
- The tool list is being rebuilt per user or per mode. Tool definitions render at the very start of the request; any variation there invalidates the system prompt and message cache along with it, not just the tools themselves.
- The model changed mid-conversation. Caches are scoped to a specific model — switching models starts a new cache from cold, even if every other part of the request is byte-identical.
- A conditional section of the system prompt is gated behind a feature flag. Every combination of flags produces a distinct prefix, which splits your cache hits across variants instead of consolidating them into one.
- A single turn adds an unusually large number of content blocks. A cache breakpoint only looks back a limited distance to find a prior matching entry; a turn that adds far more content than that can overshoot it and miss silently.
- The prefix itself is shorter than the model's minimum cacheable length. Below that floor, nothing caches and nothing errors — and that floor is not the same figure across every model.
Where to look first
Work down that list against your actual request, starting with anything that varies per request by design — timestamps and ids are by far the most common cause. If the prefix length itself is in question, check it against the minimum-cacheable-prefix table before assuming the cause is more exotic than a prefix that's simply too short for the model you're calling.
Verified 2026-08-08 against ClaudeHow facts module (src/data/facts/) — see /about/#accuracy.