Claude Cache Invalidated by Tools Change — Fix (2026) | AI Error Hub
Anthropic Claude Prompt Caching Severity: Medium Cost issue

Claude Cache Invalidated When Tools or System Change

Your cache was hitting fine, then stopped after a code deploy or tool version bump. Root cause: cache matches by exact byte-level prefix. Any change to system, tools, or content before the breakpoint invalidates the cache — including reordering tools, whitespace changes, or JSON key ordering.

Error surface: Silent cache miss after change Category: Prompt Caching Diagnostic: WRITE pattern returns Last tested: Nov 15, 2026
Quick Fix (TL;DR)

Cache is invalidated by ANY byte-level change to the prefix. Stabilize: sort tools alphabetically, freeze tool JSON schemas at deploy, use canonical JSON serialization for system prompt, and pin content before breakpoints. Version your prompt library — treat prompt changes like schema migrations.

The Symptoms You're Seeing

Post-change cache miss patterns
Cache hit rate: 95% → 0% after deploy at 2026-11-14 15:00
  → Something in the prefix changed

cache_creation_input_tokens returns nonzero on every request
  → Fresh cache being written each time = no matching prefix

Some traffic hits, some doesn't
  → Different code paths building slightly different tools/system

Cache hits then stops after tool addition
  → Adding a tool changes the tools array bytes = different prefix

After SDK upgrade: cache empty
  → SDK serialized JSON differently (key order, whitespace)

What Changes Invalidate Cache

ChangeInvalidates?Notes
System prompt textYesEven one character
Tool added/removedYesDifferent array = different prefix
Tool description editedYesBytes differ
Tool order changedYesOrder matters
Tool input_schema JSON key orderYesSerialization matters
Model name changedYesCache is per-model
anthropic-version headerYesPart of cache key
Whitespace/newlines addedYesBytes differ
Adding content AFTER breakpointNoPrefix unchanged
Reordering messages after breakpointNoPrefix unchanged

What Actually Causes This Silently

35%
Tool JSON serialized with different key orderSDK upgrade or dict iteration change → different bytes → cache miss.
25%
Dynamic tool loading orderTools loaded from filesystem in inconsistent order across restarts.
18%
Timestamp or session ID in system prompt"Current date: 2026-11-14 15:00" invalidates on every request.
12%
Adding a tool to expand capabilitiesLegitimate change, but expect fresh cache write after.
10%
Environment-specific values baked into systemDifferent env vars → different system prompt → different cache.

Fixes That Work (Tested Nov 2026)

1Canonical Tool Serialization

Python · deterministic tool order + JSON
import json def canonical_tools(tools): """Sort tools alphabetically by name; canonical JSON.""" # Sort tools list sorted_tools = sorted(tools, key=lambda t: t["name"]) # Round-trip through canonical JSON to normalize keys result = [] for tool in sorted_tools: canonical = json.dumps(tool, sort_keys=True, separators=(",", ":")) result.append(json.loads(canonical)) return result # Always apply before sending tools = canonical_tools(my_tools) # Now serialization is deterministic across SDK versions response = client.messages.create( model="claude-sonnet-4-5", tools=tools, ... )

2Remove Volatile Content From Cached Prefix

Python · move volatile data after breakpoint
# WRONG — timestamp bakes into cached system prompt system_text = f"""You are a helpful assistant. Current UTC time: {datetime.utcnow().isoformat()} Session ID: {session_id} <detailed policies>""" # → cache invalidated every request # RIGHT — put stable content in system, volatile in user message system_text = """You are a helpful assistant. <detailed policies>""" # stable, cacheable user_content = [ {"type": "text", "text": f"Context — UTC: {datetime.utcnow().isoformat()}, " f"session: {session_id}\n\nQuestion: {question}"} ] # Or use tool_use for time-sensitive queries # Claude calls get_current_time() when needed
The volatile-content audit

Scan your cached prefix for: current time/date, request IDs, session IDs, user personalization data, environment values (staging/prod), feature flags, A/B test variants, version numbers with build hashes. Move any of these to AFTER the breakpoint, or externalize via tools.

3Version-Locked Prompt Registry

Python · centralized frozen prompts
from pathlib import Path from functools import lru_cache import hashlib, json PROMPT_DIR = Path("prompts") @lru_cache(maxsize=32) def load_prompt(name, version): """Load a versioned prompt from disk. Immutable per version.""" path = PROMPT_DIR / f"{name}.v{version}.md" if not path.exists(): raise FileNotFoundError(f"Prompt not found: {path}") return path.read_text() def system_prompt_v3(): return load_prompt("assistant_system", 3) def tool_schemas_v2(): tools = json.loads(load_prompt("tools_bundle", 2)) return canonical_tools(tools) # Now upgrade explicitly and knowingly # Old callers still use v2, cache still hits # New callers use v3, get fresh cache write once # Track prompt hash for observability def prompt_fingerprint(system, tools): payload = json.dumps({"s": system, "t": tools}, sort_keys=True) return hashlib.sha256(payload.encode()).hexdigest()[:12] logger.info(f"Request fingerprint: {prompt_fingerprint(system, tools)}") # Watch for unexpected fingerprint changes = cache invalidation

Preventing This Error Going Forward

  • Canonicalize tools before sending. Sort by name, canonical JSON.
  • Remove timestamps from system prompts. Move to user turn or tool.
  • Version prompts explicitly. v1/v2/v3 files; upgrade deliberately.
  • Log prompt fingerprint. Alert on unexpected changes.
  • Test cache hit rate after every deploy. Catch regressions immediately.
  • Freeze anthropic-version header. Changing it invalidates all caches.
  • Beware SDK upgrades. New serialization = new bytes = cache miss.
AR
Tested by Ahmed R.
Researcher · AI Error Hub
Last verified: Nov 15, 2026 · SDK: anthropic 0.42

Frequently Asked Questions

Yes. Cache is prefix-match; new tools = different prefix. If you add a tool that's rarely called, consider whether the cost of forcing everyone to re-cache is worth it. Sometimes it's cheaper to run two API surfaces.

Anthropic doesn't expose a "cache probe" endpoint. Best proxy: send a real minimal request and check cache_read_input_tokens. Or maintain a fingerprint log to compare current prompt against last successful cache.

Don't — use tools instead. Tell Claude "call get_current_context() at the start of each conversation." The tool returns timestamp, user data, etc. Cache stays hot; freshness comes from tool call.

Yes. Cache is per-exact-model-identifier. Migrating from claude-sonnet-4-5 to claude-sonnet-4-6 = fresh cache. Plan model migrations with awareness of temporary cost spike.

Yes. Cache is prefix-based, so the exact sequence of messages up to the breakpoint matters. Reordering messages before the breakpoint = new prefix = cache miss. After the breakpoint, no impact on cache.