A status line + hooks combo that shows how long since your last API call, so you know when your prompt cache has expired (5-minute TTL). Works per-workspace so multiple sessions don't interfere.
Colors:
- Green: cache warm (< 3 min)
- Yellow: getting stale (3-4 min)
- Red: about to expire (4-5 min)
- Red + "expired": cache is gone, consider starting fresh
Save this to ~/.claude/cache-timer.sh:
#!/bin/sh
# Claude Code cache timer status line (per-workspace)
# Shows time since last API call with color-coded cache TTL warning
# Input: JSON via stdin (from Claude Code statusLine)
input=$(cat)
cwd=$(echo "$input" | jq -r '.workspace.current_dir // .cwd // empty')
ws_key=$(printf '%s' "${cwd:-$PWD}" | md5 | cut -c1-8)
ts_file="$HOME/.claude/.cache-ts-$ws_key"
[ -f "$ts_file" ] || exit 0
last=$(cat "$ts_file" 2>/dev/null)
now=$(date +%s)
elapsed=$((now - last))
mins=$((elapsed / 60))
secs=$((elapsed % 60))
if [ "$elapsed" -ge 300 ]; then
printf '\033[0;31m[cache expired %dm%ds]\033[0m\n' "$mins" "$secs"
elif [ "$elapsed" -ge 240 ]; then
printf '\033[0;31m[cache %dm%ds]\033[0m\n' "$mins" "$secs"
elif [ "$elapsed" -ge 180 ]; then
printf '\033[0;33m[cache %dm%ds]\033[0m\n' "$mins" "$secs"
else
printf '\033[0;32m[cache %dm%ds]\033[0m\n' "$mins" "$secs"
fichmod +x ~/.claude/cache-timer.shLinux note: Replace
md5withmd5sum | cut -c1-8in both the script and the hooks below.
If you don't have an existing status line, add:
{
"statusLine": {
"type": "command",
"command": "sh ~/.claude/cache-timer.sh"
},
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "k=$(printf '%s' \"$PWD\" | md5 | cut -c1-8); date +%s > ~/.claude/.cache-ts-$k"
}
]
}
],
"PostToolUse": [
{
"hooks": [
{
"type": "command",
"command": "k=$(printf '%s' \"$PWD\" | md5 | cut -c1-8); date +%s > ~/.claude/.cache-ts-$k"
}
]
}
]
}
}If you already have a status line script, add this snippet to it (assumes $cwd is already set from the stdin JSON):
# Cache timer — append to your existing statusline-command.sh
ws_key=$(printf '%s' "$cwd" | md5 | cut -c1-8)
ts_file="$HOME/.claude/.cache-ts-$ws_key"
if [ -f "$ts_file" ]; then
last=$(cat "$ts_file" 2>/dev/null)
now=$(date +%s)
elapsed=$((now - last))
mins=$((elapsed / 60))
secs=$((elapsed % 60))
if [ "$elapsed" -ge 300 ]; then
printf ' \033[0;31m[cache expired %dm%ds]\033[0m' "$mins" "$secs"
elif [ "$elapsed" -ge 240 ]; then
printf ' \033[0;31m[cache %dm%ds]\033[0m' "$mins" "$secs"
elif [ "$elapsed" -ge 180 ]; then
printf ' \033[0;33m[cache %dm%ds]\033[0m' "$mins" "$secs"
else
printf ' \033[0;32m[cache %dm%ds]\033[0m' "$mins" "$secs"
fi
fiOpen /hooks in Claude Code to reload config, or restart the session. The timer starts on your first message.
- UserPromptSubmit hook writes a Unix timestamp to a workspace-keyed file every time you send a message
- PostToolUse hook does the same after every tool call, since each tool result triggers a new API request that refreshes the cache
- Status line reads the timestamp file for the current workspace and shows elapsed time with color coding
- Timestamp files are keyed by an 8-char hash of the working directory (
~/.claude/.cache-ts-<hash>), so multiple sessions in different projects track independently
The status line command re-runs periodically (Claude Code controls the interval, roughly every few seconds). The timer updates passively between your messages — you don't need to do anything to see it tick up.
The timer resets when:
- You send a message (UserPromptSubmit hook)
- Claude uses any tool — Read, Edit, Bash, etc. (PostToolUse hook)
- Each of these triggers a new API call that refreshes the server-side cache
- Not a precise cache indicator. The 5-minute TTL is approximate. The actual cache lives server-side and we can't query it directly. This timer tracks the last API call as a proxy.
- Context compression resets cache too. When Claude Code compresses older messages to stay within the context window, the prompt prefix changes and the cache is invalidated regardless of timing. The timer won't catch this.
- First session needs a kick. The timer only appears after the first hook fires (your first message). No timestamp file = no timer shown.
- Stale files accumulate. Each workspace gets a
~/.claude/.cache-ts-*file that persists. Clean up withrm ~/.claude/.cache-ts-*if they bother you. - macOS vs Linux. The
md5command is macOS-specific. On Linux, replacemd5withmd5sum | cut -c1-8in the script and both hooks. - Don't use
async: trueon the PostToolUse hook. It triggers a noisy "Async hook PostToolUse completed" notification on every tool call. The command is fast enough (singledateredirect) to run synchronously.
Claude's prompt caching has a 5-minute TTL. When the cache expires, your next message re-processes the entire conversation from scratch — slower and more expensive on API plans. The timer gives you a natural decision point: if cache is expired, consider whether to continue or start a fresh session with a focused prompt.