cache-health-check
Audits your Claude Code configuration for prompt cache efficiency. Covers the 14 state vectors
tracked by src/services/api/promptCacheBreakDetection.ts, scores your setup 0-100, and outputs
ranked fixes sorted by BQ-measured break frequency.
When to trigger
- "reduce my API costs" / "lower token spending"
- "improve cache hit rate" / "why are my sessions expensive?"
- "optimize Claude Code performance"
- "diagnose cache breaks" / "cache health"
- After adding MCP servers or changing model settings
Audit workflow
Step 1 — Run the audit script
python3 ~/.claude/skills/cache-health-check/scripts/cache_health_audit.py
Produces a scored report covering all four audit domains. For machine-readable output:
python3 ~/.claude/skills/cache-health-check/scripts/cache_health_audit.py --json
Step 2 — Interpret the health score
| Score | Status | Meaning |
|---|---|---|
| 85–100 | Healthy | Cache breaking only from expected sources |
| 65–84 | Warning | 1–2 fixable break sources present |
| 40–64 | Degraded | Multiple break sources; measurable cost impact |
| 0–39 | Critical | Configuration is actively fighting the cache |
Step 3 — Apply fixes by priority
The 5 most impactful fixes, ranked by BQ break frequency (2026-03-22):
Fix 1 — Tool schema instability (77% of tool breaks)
- Cause: MCP servers that generate dynamic tool descriptions (timestamps, session IDs,
file counts embedded in tool
descriptionfields) - Fix: Pin MCP server versions; audit tool descriptions for volatile content; prefer servers that return stable schemas
- Detection:
perToolHashesvector changes between sessions
Fix 2 — System prompt changes (CLAUDE.md)
- Cause: Embedded dates, absolute paths, or dynamic content in CLAUDE.md
- Fix: Make CLAUDE.md purely static; move dynamic context to runtime tool calls
- Detection:
systemHashandsystemCharCountvectors - Deep analysis: run
~/.claude/skills/prompt-architect/scripts/claudemd_audit.py
Fix 3 — Global cache strategy instability
- Cause: Sessions that sometimes have MCP tools and sometimes don't flip between
tool_basedandsystem_promptstrategies, breaking the cache boundary location - Fix: Keep MCP server set consistent; if you disable servers, use a stable subset
- Detection:
globalCacheStrategyvector
Fix 4 — Model string variance
- Cause:
ANTHROPIC_MODELenv var ormodelfield varies between sessions or is overridden mid-session - Fix: Pin model in
settings.json; avoid per-project model overrides that differ from the global default - Detection:
modelvector
Fix 5 — Extra body params
- Cause:
CLAUDE_CODE_EXTRA_BODYenv var contains session-specific values (timestamps, random seeds, request IDs) - Fix: Keep extra body params fully static or remove them; never inject dynamic values
- Detection:
extraBodyHashvector
Step 4 — Deep CLAUDE.md analysis (optional)
For detailed CLAUDE.md restructuring recommendations, run the prompt-architect audit:
python3 ~/.claude/skills/prompt-architect/scripts/claudemd_audit.py
This provides full section-level classification (static vs. dynamic) and a restructured output with the stable prefix / dynamic suffix boundary clearly marked.
The 14 tracked state vectors
These are the fields in PreviousState / PendingChanges in promptCacheBreakDetection.ts.
A change in any of them may or may not break the cache depending on whether the changed
field affects the serialized prompt bytes.
| # | Vector | Break risk | Notes |
|---|---|---|---|
| 1 | systemHash |
High | Full system prompt content hash |
| 2 | toolsHash |
High | Aggregate hash of all tool schemas |
| 3 | cacheControlHash |
Medium | Catches global↔org flips, 1h↔5m TTL flips |
| 4 | perToolHashes |
High | Per-tool schema hash (77% of tool breaks) |
| 5 | systemCharCount |
Low | Char delta — magnitude indicator only |
| 6 | model |
High | Model string; different model = different cache |
| 7 | fastMode |
Medium | Fast mode toggle changes effort resolution |
| 8 | globalCacheStrategy |
High | tool_based vs system_prompt vs none |
| 9 | betas |
Medium | Beta header list changes |
| 10 | autoModeActive |
None | Latched sticky-on — should NOT break cache |
| 11 | isUsingOverage |
None | Session-stable — should NOT break cache |
| 12 | cachedMCEnabled |
None | Latched sticky-on — should NOT break cache |
| 13 | effortValue |
Medium | Resolved effort: env → options → model default |
| 14 | extraBodyHash |
Medium | CLAUDE_CODE_EXTRA_BODY + internal params |
Vectors 10, 11, 12 are intentionally latched — they change at most once per session and are not expected to break the cache.
Cache break detection thresholds
From promptCacheBreakDetection.ts:
- Break condition:
cacheReadTokens < prevCacheRead × 0.95ANDtokenDrop ≥ 2000 - Excluded models: Haiku (different caching behaviour — not tracked)
- TTL values: 5 minutes (default tier), 1 hour (overage tier)
- Server-side break heuristic: no client-side changes detected AND gap < 5 min → logged as "likely server-side (prompt unchanged, <5min gap)"
Tracked query sources
Monitored: repl_main_thread, compact (maps to main thread), sdk,
agent:custom, agent:default, agent:builtin
Not tracked (short-lived forks): speculation, session_memory, prompt_suggestion
Common cache-busting sources in CLAUDE.md
| Source | Example | Fix |
|---|---|---|
| Embedded date | "Last updated: April 2026" | Delete it |
| Absolute path | /Users/fxx/projects/app/ |
Use relative path |
| Dynamic tool list | MCP server with session-specific tools | Pin server or remove |
| Model override | Per-project model different from global |
Unify to one model |
| Effort toggle | alwaysThinkingEnabled changed mid-project |
Pick one and keep it |
| Extra body params | CLAUDE_CODE_EXTRA_BODY with request IDs |
Use static params only |
| Beta header churn | Experimental betas toggled on/off | Stabilize beta list |