If you're using a custom statusline script, you can add cache health monitoring to catch when your prompt cache is getting thrashed. Here's what to add and what it means.
What it tracks: The ratio between cache_read_input_tokens (cheap, reused from cache) and cache_creation_input_tokens (expensive, rebuilding cache). When cache reads drop to zero while creation stays high, you're paying full price every turn instead of getting the ~90% cache discount.
Add this near the top where you parse the statusline JSON input:
# Extract context_window data
context_window=$(echo "$input" | jq -c '.context_window // null')
current_usage=$(echo "$input" | jq -c '.context_window.current_usage // null')
# Calculate context percentage
context_pct=""
if [ "$context_window" != "null" ]; then
context_window_size=$(echo "$context_window" | jq -r '.context_window_size // 0')
if [ "$current_usage" != "null" ]; then
input_tokens=$(echo "$current_usage" | jq -r '.input_tokens // 0')
cache_creation=$(echo "$current_usage" | jq -r '.cache_creation_input_tokens // 0')
cache_read=$(echo "$current_usage" | jq -r '.cache_read_input_tokens // 0')
total_tokens=$((input_tokens + cache_creation + cache_read))
if [ "$context_window_size" -gt 0 ]; then
context_pct=$((total_tokens * 100 / context_window_size))
fi
fi
fiAdd this where you build your status line segments:
# Cache Health Indicator
# Detects broken cache: cache_read stuck at 0 while cache_creation is high
if [ "$current_usage" != "null" ] && [ -n "$context_pct" ]; then
STATE_DIR="/tmp/claude-code-state"
mkdir -p "$STATE_DIR"
CACHE_HEALTH_FILE="${STATE_DIR}/${SESSION_ID}-cache-health.json"
# Track consecutive zero-read checks
if [ -f "$CACHE_HEALTH_FILE" ]; then
prev_checks=$(jq -r '.checks // 0' "$CACHE_HEALTH_FILE" 2>/dev/null)
prev_zero_reads=$(jq -r '.zero_reads // 0' "$CACHE_HEALTH_FILE" 2>/dev/null)
else
prev_checks=0
prev_zero_reads=0
fi
new_checks=$((prev_checks + 1))
if [ "$cache_read" -eq 0 ] && [ "$cache_creation" -gt 0 ]; then
new_zero_reads=$((prev_zero_reads + 1))
else
# Reset on any successful cache read
new_zero_reads=0
fi
# Persist state across refreshes
printf '{"checks":%d,"zero_reads":%d,"last_read":%s,"last_creation":%s,"timestamp":"%s"}\n' \
"$new_checks" "$new_zero_reads" "$cache_read" "$cache_creation" \
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$CACHE_HEALTH_FILE" 2>/dev/null
# Skip first 2 checks (cold start -- no cache exists yet, zero reads is normal)
# After 3+ consecutive zero-read checks, warn about cache thrashing
if [ "$new_checks" -gt 2 ] && [ "$new_zero_reads" -ge 3 ]; then
status_parts+=("${RED}CACHE MISS${RESET}")
elif [ "$new_checks" -gt 2 ] && [ "$cache_read" -gt 0 ]; then
# Show cache hit ratio when healthy
cache_total=$((cache_read + cache_creation))
if [ "$cache_total" -gt 0 ]; then
cache_hit_pct=$((cache_read * 100 / cache_total))
dollar='$'
if [ "$cache_hit_pct" -gt 80 ]; then
status_parts+=("${GREEN}${cache_hit_pct}%${dollar}${RESET}")
elif [ "$cache_hit_pct" -gt 50 ]; then
status_parts+=("${YELLOW}${cache_hit_pct}%${dollar}${RESET}")
else
status_parts+=("${RED}${cache_hit_pct}%${dollar}${RESET}")
fi
fi
fi
fi92%$in green -- healthy, 92% of tokens hitting cache (saving money)45%$in red -- poor cache hit rate, you're overpayingCACHE MISSin red -- cache is completely thrashed, zero reads for 3+ consecutive checks
A broken cache means you're paying full input token price every turn instead of getting the ~90% discount. It also increases latency. I found that certain workflows (like batch tool calls in quick succession) consistently thrash the cache, and was able to restructure them once I could actually see it happening in real time.
SESSION_ID should match however you identify your session -- tmux pane ID, env var, or a hash of the working directory.