Claude prompt caching cache miss — cache_control not hitting, no 90% discount (2026) — Fix Guide (2026) | AI Error Hub
Home Providers Claude Prompt cache miss
Claude Prompt Caching · Cache Miss Severity: Medium HTTP 200

Claude cache_control — no cache hit, paying full input token cost

Prompt caching is one of the most valuable Claude features and the one most commonly misconfigured. A working cache cuts input cost by 90%; a broken one silently pays full price.

By Ahmed R. · Senior AI Infrastructure Engineer Published 2026-07-26 Verified 2026-07-26

Quick fix (TL;DR)

Resolution: Claude prompt caching stores prefix tokens for 5 minutes (default) or 1 hour (extended beta) after the first request. To trigger a hit, subsequent requests must have byte-identical content up to and including the cache_control block, with the same model. Fix by (a) reading cache_creation_input_tokens and cache_read_input_tokens in usage, (b) placing cache_control after the last stable content block (system prompt, tools, big documents), and (c) never putting user-variable content before the cache breakpoint.

Real error messages you'll see

These are the exact strings returned by the Claude API service and its SDKs when this error occurs. Copy-paste-searching any of them should land on this page.

No cache hit — full price paid
Message(
    id="msg_...",
    usage=Usage(
        input_tokens=12000,
        cache_creation_input_tokens=0,
        cache_read_input_tokens=0,     # ← ZERO — no cache hit
        output_tokens=200
    )
)
Cache created but never read — one-shot request
Request 1: cache_creation_input_tokens=11000, cache_read_input_tokens=0
Request 2: cache_creation_input_tokens=11000, cache_read_input_tokens=0
           (same content — but different from Request 1 in some byte)

# You paid 25% surcharge on both creates, got zero reads. Net LOSS.
400 — cache_control on wrong block type
anthropic.BadRequestError: Error code: 400 - {'type': 'error', 'error': {'type': 'invalid_request_error', 'message': 'cache_control is not supported on text blocks with type=image.'}}

Reference

Cache pricing math — worth it or not

Token typeCost vs standard inputWhen paid
Standard inputEvery uncached input token
Cache write (creation)1.25×First time a prefix is stored
Cache read (hit)0.10×Every request that hits the cached prefix

Where cache_control can be placed

LocationSupported?Common use
End of system promptYesLong instructions / persona
End of tools arrayYesLarge tool schemas
End of a user message text blockYesCached document context
End of an assistant message blockYesCached conversation prefix
Image blocksNo — image data cannot carry cache_control
Multiple positions in one requestYes — up to 4 breakpointsNested cache tiers

Root causes, ranked by frequency

Based on developer reports across Claude API forums, GitHub issues, and Anthropic community during 2025–2026.

  • 30%
    Prefix is not byte-identical between requests. A timestamp, request ID, or dynamic string inside the system prompt makes every request unique — cache never matches.
  • 18%
    cache_control placed after user-variable content. Everything before the breakpoint must be identical across requests; the user's new question must come after.
  • 14%
    Cache expired. Default TTL is 5 minutes. If requests spread over hours, only the first in each 5-minute window is a hit.
  • 10%
    Different model. Cache is model-specific — claude-opus-4-7 and claude-sonnet-5 maintain separate caches.
  • 8%
    System prompt provided as string vs array. system=[{"type":"text","text":"..."}] is required for cache_control; plain system="..." silently cannot cache.
  • 7%
    Different beta headers. anthropic-beta: prompt-caching-2024-07-31 creates a separate cache namespace from extended-cache-ttl-2025-04-11.
  • 6%
    Too few tokens before the breakpoint. Cache requires a minimum ~1024 tokens (Sonnet) or ~2048 (Opus) before the cache_control marker.
  • 7%
    Prompt caching not enabled at the org level. Some workspaces require an admin toggle before caching is honoured.

Fixes — copy-paste solutions

Fix #1

Structure the prompt so cache_control comes after all stable content

Stable → cache_control → variable. That is the entire pattern.

Put the stable prefix (system prompt, tools, cached documents) up front. Place cache_control on the last stable block. Put user-variable content after the breakpoint.

correct_cache_structure.py
import anthropic

client = anthropic.Anthropic()

# ✓ CORRECT — stable content before cache_control, variable after
def ask_over_document(document: str, user_question: str):
    return client.messages.create(
        model="claude-opus-4-7",
        max_tokens=1000,
        system=[
            {
                "type": "text",
                "text": (
                    "You are a research assistant. Answer strictly from the document.\n\n"
                    f"<document>\n{document}\n</document>"
                ),
                "cache_control": {"type": "ephemeral"},  # ← breakpoint HERE
            },
        ],
        messages=[
            {"role": "user", "content": user_question},   # ← variable AFTER breakpoint
        ],
    )

# First call: document is cached (cache_creation_input_tokens > 0)
r1 = ask_over_document(long_document, "Who is the CEO?")
print("create:", r1.usage.cache_creation_input_tokens,
      "read:",   r1.usage.cache_read_input_tokens)

# Subsequent calls with the SAME document: cache_read_input_tokens > 0 (90% off)
r2 = ask_over_document(long_document, "What was 2024 revenue?")
print("create:", r2.usage.cache_creation_input_tokens,
      "read:",   r2.usage.cache_read_input_tokens)
The document must be byte-identical between calls. Even a trailing newline difference breaks the cache. Serialize documents from a single source of truth.
Fix #2

Debug cache misses with per-request usage tracking

The cache_creation vs cache_read fields tell you exactly what is happening.

Every response includes cache_creation_input_tokens (paid at 1.25×) and cache_read_input_tokens (paid at 0.10×). Log both on every request and alert when hit rate drops below expected.

cache_tracking.py
import anthropic
import logging
from dataclasses import dataclass

log = logging.getLogger(__name__)
client = anthropic.Anthropic()

@dataclass
class CacheStats:
    uncached_input: int
    cache_create: int
    cache_read: int

    @property
    def hit_rate(self) -> float:
        total = self.cache_create + self.cache_read + self.uncached_input
        return self.cache_read / total if total else 0.0

    def cost_estimate(self, model_input_price_per_mtok: float) -> float:
        return (
            self.uncached_input * (model_input_price_per_mtok / 1_000_000)
            + self.cache_create * (model_input_price_per_mtok * 1.25 / 1_000_000)
            + self.cache_read * (model_input_price_per_mtok * 0.10 / 1_000_000)
        )

def call_with_tracking(**kwargs) -> tuple[anthropic.types.Message, CacheStats]:
    response = client.messages.create(**kwargs)
    usage = response.usage
    stats = CacheStats(
        uncached_input=usage.input_tokens,        # NOT including cache read/create
        cache_create=getattr(usage, "cache_creation_input_tokens", 0) or 0,
        cache_read=getattr(usage, "cache_read_input_tokens", 0) or 0,
    )
    log.info("cache: create=%d read=%d uncached=%d hit_rate=%.1f%%",
             stats.cache_create, stats.cache_read, stats.uncached_input,
             stats.hit_rate * 100)

    if stats.cache_create > 0 and stats.cache_read == 0:
        log.warning("CACHE MISS — paid create but no read. Prefix probably changed.")
    if stats.cache_read > 0 and stats.cache_create > 0:
        log.info("PARTIAL HIT — matched only some blocks. Reorganise cache_control?")

    return response, stats

response, stats = call_with_tracking(
    model="claude-opus-4-7",
    max_tokens=500,
    system=[{"type": "text", "text": SYSTEM_PROMPT,
             "cache_control": {"type": "ephemeral"}}],
    messages=[{"role": "user", "content": user_input}],
)
A create-only response with no reads means one of three things: (1) first call in the window, (2) content changed since last call, (3) cache expired. The next call within 5 minutes should show a read.
Fix #3

Use extended cache TTL for hourly or daily-workload use cases

The 1-hour cache changes the economics for workflow-style traffic.

Enable the extended-cache-ttl beta and set ttl: "1h". Reads within 1 hour cost 90% off; creates cost 2× standard. Only worth it for high-reuse prefixes hit many times per hour.

extended_ttl_cache.py
import anthropic

client = anthropic.Anthropic(
    default_headers={
        # Extended TTL beta — check current header name in Anthropic docs
        "anthropic-beta": "extended-cache-ttl-2025-04-11",
    }
)

# Extended TTL is best for prompts that are read many times per hour
# (agent memory, per-user personas, long-lived context)
response = client.messages.create(
    model="claude-opus-4-7",
    max_tokens=500,
    system=[
        {
            "type": "text",
            "text": VERY_LARGE_STABLE_PROMPT,   # e.g. 40K-token knowledge base
            "cache_control": {
                "type": "ephemeral",
                "ttl": "1h",                    # 1-hour cache (vs default 5m)
            },
        },
    ],
    messages=[
        {"role": "user", "content": user_question},
    ],
)

# Breakeven: extended TTL wins when reads per creation > ~13
# (2x create cost / 0.10x read savings = 20 reads pay back one create at extended
# vs 0.25x/0.10x = 3 reads pay back at default TTL)
Extended TTL is not always cheaper. Rule of thumb: use 1h TTL only when the same prefix is queried ≥15 times per hour. For bursty traffic, default 5m TTL is more cost-effective.

Prevention checklist

Ship these seven safeguards once and this error stops appearing in your logs.

  • Provide system as an array with an explicit cache_control — never as a plain string.
  • Place cache_control after the last stable block; put user-variable content strictly after.
  • Serialize cached content from a single source of truth — even one trailing newline breaks the cache.
  • Log cache_creation_input_tokens and cache_read_input_tokens on every response; alert when hit rate drops.
  • Use the default 5-minute TTL unless you have measured ≥15 reads/hour on the same prefix.
  • Test cache behaviour in staging with a scripted 3-call sequence before shipping — miss patterns are silent.
  • Split large prompts into up to 4 cache breakpoints for nested reuse (org-wide prefix + user-specific prefix + session prefix).

Frequently asked questions

Default TTL is 5 minutes since the last read. The 1-hour extended TTL is available via the extended-cache-ttl beta header. Each read resets the timer, so an actively-hit cache lives longer than an idle one.
Yes — cache reads still count against your ITPM (input tokens per minute) rate limit at the standard rate, not the discounted cost rate. Caching saves money, not throughput headroom.
Yes — put cache_control on the last tool in the tools array. All tools before the marker (and the system prompt before them) get cached. Very effective for agent workflows with large tool schemas.
Approximately 1,024 tokens for Sonnet and Haiku, 2,048 for Opus. Below the minimum the request succeeds but no cache is created. Combine short chunks into a single larger block before the breakpoint.
Cache is scoped to your organization. Two users in the same org submitting byte-identical prompts share the cache. Users in different orgs never share.

Get the weekly AI-error digest

New fixes, provider status recaps, and one deep tutorial — every Tuesday. 8,400+ engineers.