Skip to content

Instantly share code, notes, and snippets.

@gadenbuie
Created February 26, 2026 17:58
Show Gist options
  • Select an option

  • Save gadenbuie/e60c44cdb321b8e5a1eb55bd993bc817 to your computer and use it in GitHub Desktop.

Select an option

Save gadenbuie/e60c44cdb321b8e5a1eb55bd993bc817 to your computer and use it in GitHub Desktop.
Add a Claude Code cache timer to your tmux config

Claude Code Prompt Caching Overview

Default Behavior

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 available

When Cache Writes Happen

Cache writes happen when the API processes the request and begins streaming a response — not on user input submission.

  1. User submits message → API request fires
  2. API checks for a cache hit on the prefix up to the breakpoint
  3. Cache miss: full prompt is processed, then cache write occurs as the response begins
  4. Cache hit: cached prefix is reused, response is generated from that point

TTL Behavior

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.

During the Tool Loop

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.

Cache Breakpoint Structure

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

Minimum Cacheable Token Thresholds

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.


Cache Timer

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.

How It Works

  • CC hooks (UserPromptSubmit, PostToolUse, Stop) write a Unix timestamp to ~/.claude/session-env/<session_id>/cache_hit_timestamp on every API boundary
  • SessionStart writes a tmux pane → session ID mapping to ~/.claude/tmux-panes/$TMUX_PANE
  • SessionEnd cleans 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:SS 4–5 min, red ✗ MM:SS > 5 min, red Cache expired after 60 min

Files

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

Installation

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.

1. Create the scripts

~/.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}"

2. Register the hooks in ~/.claude/settings.json

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" }
        ]
      }
    ]
  }
}

3. Add to tmux (~/.tmux.conf)

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

Invocation

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.

Data Layout

~/.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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment