Claude Code uses prompt caching automatically. The default TTL is 5 minutes, with an optional extended 1-hour cache available at extra cost.
To disable:
DISABLE_PROMPT_CACHING=1 # Disables all caching
DISABLE_PROMPT_CACHING_SONNET=1 # Per-model variants also availableCache writes happen when the API processes the request and begins streaming a response — not on user input submission.
- User submits message → API request fires
- API checks for a cache hit on the prefix up to the breakpoint
- Cache miss: full prompt is processed, then cache write occurs as the response begins
- Cache hit: cached prefix is reused, response is generated from that point
The 5-minute TTL starts at the cache write, but resets on every cache hit at no additional cost. It's 5 minutes of inactivity, not a fixed window from creation. As long as requests hit the same cache prefix within each 5-minute window, the cache lives indefinitely.
Claude Code's agentic loop makes multiple sequential API calls (one per tool use/result cycle). Each is a full API round-trip, so:
- Each tool loop iteration can produce a cache write (advancing the breakpoint)
- Each iteration also gets a cache hit on the prefix from prior turns
- The TTL is continuously refreshed throughout an active tool loop
The cache goes stale only if you stop interacting for 5 minutes.
Breakpoints are placed at the end of the last cacheable block, in this priority order: Tools → System → Messages. As the conversation grows, the breakpoint automatically advances.
Request N: [tools][system][msg1][msg2]...[msgN-1] ← cache read
[msgN] ← cache write
Request N+1: [tools][system][msg1][msg2]...[msgN] ← cache read
[msgN+1] ← cache write
| Model | Minimum tokens |
|---|---|
| Sonnet 4.x | 1,024 |
| Opus 4.x | 4,096 |
| Haiku 4.x | 4,096 |
Conversations shorter than these thresholds won't be cached even if breakpoints are set.
A set of scripts and hooks that display time elapsed since the last Claude Code cache hit, scoped per session. Useful for knowing when you're approaching the 5-minute cache expiry.
- CC hooks (
UserPromptSubmit,PostToolUse,Stop) write a Unix timestamp to~/.claude/session-env/<session_id>/cache_hit_timestampon every API boundary SessionStartwrites a tmux pane → session ID mapping to~/.claude/tmux-panes/$TMUX_PANESessionEndcleans up the pane mapping~/.claude/cache-timer <pane_id>reads the timestamp for that pane and outputs a colored tmux format string- Colors: green
⚡MM:SS< 4 min, yellow⚡MM:SS4–5 min, red✗ MM:SS> 5 min, redCache expiredafter 60 min
| File | Purpose |
|---|---|
~/.claude/cache-timer |
Display script |
~/.claude/hooks/cache-timer-record.sh |
Writes timestamp on each API event |
~/.claude/hooks/cache-timer-session-start.sh |
Writes pane→session mapping at session start |
~/.claude/hooks/cache-timer-session-end.sh |
Cleans up pane mapping at session end |
This setup assumes that you're using tmux and running Claude Code in tmux. To install the cache timer feature, tell Claude Code to read this gist and implement the following steps. This lets you also customize this to your particular setup.
~/.claude/cache-timer (make executable: chmod +x ~/.claude/cache-timer)
#!/usr/bin/env bash
EXPIRE_SECONDS=$(( 60 * 60 ))
PANE_FILE="${HOME}/.claude/tmux-panes/${1}"
[[ -f "$PANE_FILE" ]] || exit 0
SESSION_ID=$(< "$PANE_FILE")
CACHE_FILE="${HOME}/.claude/session-env/${SESSION_ID}/cache_hit_timestamp"
[[ -f "$CACHE_FILE" ]] || exit 0
LAST_HIT=$(< "$CACHE_FILE")
ELAPSED=$(( $(date +%s) - LAST_HIT ))
if (( ELAPSED >= EXPIRE_SECONDS )); then
printf "#[fg=red]Cache expired#[default]\n"
exit 0
fi
MINUTES=$(( ELAPSED / 60 ))
SECS=$(( ELAPSED % 60 ))
if (( ELAPSED < 240 )); then
printf "#[fg=green]⚡%02d:%02d#[default]\n" "$MINUTES" "$SECS"
elif (( ELAPSED < 300 )); then
printf "#[fg=yellow]⚡%02d:%02d#[default]\n" "$MINUTES" "$SECS"
else
printf "#[fg=red]✗ %02d:%02d#[default]\n" "$MINUTES" "$SECS"
fi~/.claude/hooks/cache-timer-record.sh (make executable)
#!/usr/bin/env bash
SESSION_ID=$(jq -r '.session_id // empty')
[[ -z "$SESSION_ID" ]] && exit 0
mkdir -p "${HOME}/.claude/session-env/${SESSION_ID}"
date +%s > "${HOME}/.claude/session-env/${SESSION_ID}/cache_hit_timestamp"~/.claude/hooks/cache-timer-session-start.sh (make executable)
#!/usr/bin/env bash
[[ -z "$TMUX_PANE" ]] && exit 0
SESSION_ID=$(jq -r '.session_id // empty')
[[ -z "$SESSION_ID" ]] && exit 0
mkdir -p "${HOME}/.claude/tmux-panes"
echo "$SESSION_ID" > "${HOME}/.claude/tmux-panes/${TMUX_PANE}"~/.claude/hooks/cache-timer-session-end.sh (make executable)
#!/usr/bin/env bash
[[ -z "$TMUX_PANE" ]] && exit 0
rm -f "${HOME}/.claude/tmux-panes/${TMUX_PANE}"Add cache-timer-record.sh to UserPromptSubmit, PostToolUse, and Stop. Add the session hooks to SessionStart and SessionEnd. Each is appended as a second entry alongside any existing hooks:
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{ "type": "command", "command": "bash ~/.claude/hooks/cache-timer-record.sh" }
]
}
],
"PostToolUse": [
{
"matcher": "*",
"hooks": [
{ "type": "command", "command": "bash ~/.claude/hooks/cache-timer-record.sh" }
]
}
],
"Stop": [
{
"hooks": [
{ "type": "command", "command": "bash ~/.claude/hooks/cache-timer-record.sh" }
]
}
],
"SessionStart": [
{
"hooks": [
{ "type": "command", "command": "bash ~/.claude/hooks/cache-timer-session-start.sh" }
]
}
],
"SessionEnd": [
{
"hooks": [
{ "type": "command", "command": "bash ~/.claude/hooks/cache-timer-session-end.sh" }
]
}
]
}
}The script handles the icon and color; add a CC label prefix directly in the config:
set -g status-interval 1
set -g status-right " CC#(~/.claude/cache-timer #{pane_id}) | %H:%M "
Then reload: tmux source-file ~/.tmux.conf
cache-timer takes a single argument: the tmux pane ID. tmux expands #{pane_id} before running the command, so the status-right automatically scopes to whichever pane is currently focused.
~/.claude/cache-timer %3 # shows timer for pane %3
Called with no argument or an unknown pane ID, it exits silently — nothing is displayed.
~/.claude/
├── cache-timer # display script
├── hooks/
│ ├── cache-timer-record.sh # writes timestamp on API events
│ ├── cache-timer-session-start.sh # writes pane→session mapping
│ └── cache-timer-session-end.sh # removes pane→session mapping
├── session-env/
│ └── <session_id>/
│ └── cache_hit_timestamp # Unix epoch of last cache hit
└── tmux-panes/
└── %3 # contains session_id for pane %3