Skip to content

Instantly share code, notes, and snippets.

@jesserobbins
Last active April 16, 2026 01:18
Show Gist options
  • Select an option

  • Save jesserobbins/ff344a13f3b90cddb8e6b1e19e7e604e to your computer and use it in GitHub Desktop.

Select an option

Save jesserobbins/ff344a13f3b90cddb8e6b1e19e7e604e to your computer and use it in GitHub Desktop.
Claude Code cache timer - status line showing prompt cache TTL with color-coded warnings

Claude Code Cache Timer

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

Setup

1. Save the status line script

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"
fi
chmod +x ~/.claude/cache-timer.sh

Linux note: Replace md5 with md5sum | cut -c1-8 in both the script and the hooks below.

2. Add to ~/.claude/settings.json

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
fi

3. Reload

Open /hooks in Claude Code to reload config, or restart the session. The timer starts on your first message.

How it works

  • 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

Refresh behavior

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

Known limitations

  • 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 with rm ~/.claude/.cache-ts-* if they bother you.
  • macOS vs Linux. The md5 command is macOS-specific. On Linux, replace md5 with md5sum | cut -c1-8 in the script and both hooks.
  • Don't use async: true on the PostToolUse hook. It triggers a noisy "Async hook PostToolUse completed" notification on every tool call. The command is fast enough (single date redirect) to run synchronously.

Why this matters

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment