claude-thermostat
currently reading $0.00
Claude Code · Stop & SessionEnd hooks

Keep your sessions
from running hot.

A pair of shell hooks that watch your Claude Code session cost in real time, and write a post-session cooldown report telling you what would have been cheaper.

EventStop / SessionEnd
Cost sourceTranscript JSONL
Models pricedOpus · Sonnet · Haiku
Footprint5 scripts · 2 skills · 1 py lib
§ 01 · anatomy

A thermostat, exactly.

§ 01.a
Setpoint
$CLAUDE_THERMOSTAT_COST_CENTS

The dollar ceiling you're willing to spend before being interrupted. Defaults to $50, raise or lower it to suit. The only setpoint enabled by default. Time, turns, context size, and a rolling subscription-window token sum are available as opt-in. A sudden per-turn cache drop (≥30pp below the session average) is detected as an always-on trigger without a setpoint — it flags a context reshuffle that just made the next turn much more expensive.

§ 01.b
Sensor
transcript JSONL parser

Reads actual token usage per turn, applies per-model pricing, dedupes re-appended assistant messages by message.id. The reading matches what Anthropic actually bills.

§ 01.c
Actuator
exit 2 → Claude relays alert

When the setpoint trips, the hook exits with code 2. Claude Code surfaces the message as its next response, offers /compact, /clear, /model sonnet, or "keep going" — the user picks.

§ 01.d
Deadband
$CLAUDE_THERMOSTAT_COOLDOWN_TURNS

Hysteresis, measured in turns. After firing, the alert won't fire again for N more turns of actual work — so a single breach doesn't generate a stream of identical alerts.

§ 02 · the moment of intervention

The alert, verbatim.

stop hook · exit 2 thermostat.sh
🌡 thermostat · turn 14 · 9m · $52.18 · 142K ctx · 67% cached estimated session cost: $52.18 antipattern: re-Read of 'src/billing/router.ts' ×4 in last 30 tool calls Tell the user and ask them what they'd like to do: /compact — shrink context (best when task is ongoing) Delegate to a subagent — keep Read/Bash/Grep out of main context; cheaper turns /model sonnet — ~3.3× cheaper; switch for routine turns Lower autoCompactThreshold (default) → 0.70 in ~/.claude/settings.json — auto-compact fires at 70% context fill /clear — fresh start (best when pivoting tasks) Continue

Claude sees this. So do you.

Because the hook fires on Stop and exits non-zero, Claude reactivates and reads the alert as part of its next turn. It surfaces the cost, the trigger, and a menu of context-aware actions.

The alert is the prompt; you're the one in charge. /compact, /clear, or close-and-pivot are all one keystroke away.

§ 03 · the effect

Running cool, visualized.

Cumulative session cost — sample 30-turn session

unmonitored with thermostat setpoint
$0 $25 $50 $75 $100 turn 0 10 14 20 25 30 SETPOINT $50 ALERT — TURN 14 user: /compact ALERT — TURN 25 user: /clear + new task $96 $41
Unmonitored end-cost
$96.34
30 turns, no interventions, Opus-heavy.
With thermostat
$41.02
Two alerts — one /compact, one /clear on task pivot.
Saved (sample)
$55.32
Illustrative numbers from a single session. Your mileage varies with the work.
§ 04 · post-session

A cooldown report, for next time.

Cooldown report

session 6788b2fd · ended 2026-05-22T15:51 · reason: clear
Cost
$13.32 fable-5 · 100%
Duration
116 min 7 turns
Tokens read from cache
552K across 7 API calls
Cache hit rate
96% healthy — minimal context churn
Suggestions for next session
  • Model choice — 4 Fable turns produced <500 output tokens. These were small lookups; Sonnet (~3.3× cheaper) or Haiku (~10× cheaper) would have handled them. Use /model sonnet for routine work.
  • Skill candidate — Read docs/pricing-spec.md 5× — convert to a skill at ~/.claude/skills/ so it loads on demand rather than re-reading every turn.
  • Tool choice — 38 Grep/Read/Glob calls (61% of all tool use). Try codegraph_explore for natural-language lookups; one call usually replaces a chain.
  • Prompt pattern — 12/19 prompts were <60 chars. Many turn-based corrections — one detailed first prompt often beats many small follow-ups and reuses cache better.
  • Long, hot session — 24 turns, final context 168K — near auto-compaction. Commit + push to checkpoint, then start fresh: git history carries context forward more cheaply than a compacted transcript. Or steer it with /compact <what to keep>.
  • Extended thinking — 31 thinking blocks and 74K output tokens. Thinking bills as output; for routine work, lower reasoning effort with /effort or MAX_THINKING_TOKENS.
  • MCP result accumulation — 7 claude_ai_Atlassian_2 calls in the last 30 tool calls. Each response stays in context for the rest of the session; run /compact to flush before continuing.
  • MCP surface — tools used across 2 servers. Run /context to see what each costs, /mcp to disable idle ones, and prefer CLI tools (gh, aws) — they add no per-tool listing.
  • .claudeignore candidate — 9 reads/greps into node_modules and dist. Add them to .claudeignore so they stop burning context.
  • Inline deterministic work — 3 turns with ≥4000 output tokens and no Bash/Write/Edit. Model may have computed or reformatted data inline instead of scripting it. Install the deterministic-toolkit skill for mechanical work: parsing, converting formats, deduping, aggregating, validating, diffing. Scripts are deterministic; in-context arithmetic is not.
§ 05 · skills

Ask anytime, on demand.

Two Claude Code skills let you query thermostat data mid-session, without waiting for a setpoint to trip. Symlink the skill files into ~/.claude/skills/ and they become slash commands in every session.

/thermostat · current session thermostat-status.sh
thermostat status · session c940239e Turns: 15 Age: 44 min Cost: $0.63 (subscription-estimated) Context: 139K tokens (last turn) Cache hit: 97% session avg, 100% last turn Model: sonnet-5 Tokens: in=64 cw=85K cr=2.8M out=21K

Pull, not push.

The Stop hook alerts you when a threshold trips. The skill answers your question whenever you ask — useful mid-session when you want to know where you stand without waiting for the setpoint.

Powered by thermostat-status.sh: the same transcript parser as the hook, wired to a read-only status query with no side effects.

/thermostat-week · 7-day trend weekly-trend.sh
thermostat weekly trend · 7 days ending 2026-05-26 Date Sessions Cost Turns Top suggestions ────────────────────────────────────────────── 2026-05-26 9 $182.51 261 skill(2), model(1) 2026-05-25 5 $91.20 142 context(2) 2026-05-23 2 $25.13 62 — ────────────────────────────────────────────── Total 16 $298.84 465 Recurring: skill(2), context(2), model(1)

Per-session → per-week.

Cooldown reports accumulate in ~/.claude/thermostat/reports/. The weekly trend reads them all and shows which suggestion categories keep recurring — the ones worth fixing structurally, not just session-to-session.

Runs standalone too: ./weekly-trend.sh 14 for a fortnight, or ./weekly-trend.sh --markdown for a GitHub-flavored table.

§ 06 · install

Wire it up once.

Clone the repo ↗

Put it anywhere on disk you'd keep a personal hook. You only need the shell scripts and the python helper — no install step.

Add two hooks to settings.json

Open ~/.claude/settings.json and register the Stop and SessionEnd hooks. Restart any open Claude Code sessions for them to take effect.

Optional: set your setpoint

Drop a config file at ~/.claude/thermostat/config.env, or set CLAUDE_THERMOSTAT_COST_CENTS in your shell. Default is $50.

Optional: install the skills

Symlink the two skill files into ~/.claude/skills/ to enable /thermostat (live status) and /thermostat-week (7-day trend) as slash commands in any session.

~/.claude/settings.json# Stop hook fires after every Claude response.
# exit 2 surfaces the alert as Claude's next message.
"Stop": [{
  "hooks": [{
    "type":    "command",
    "command": "/abs/path/to/claude-thermostat/claude-thermostat.sh"
  }]
}],
"SessionEnd": [{
  "hooks": [{
    "type":    "command",
    "command": "/abs/path/to/claude-thermostat/cooldown-report.sh"
  }]
}]

~/.claude/thermostat/config.env# All optional. Cost is the only enabled trigger by default.
CLAUDE_THERMOSTAT_COST_CENTS=3000     # fire at $30 instead of $50
CLAUDE_THERMOSTAT_COOLDOWN_TURNS=15
CLAUDE_THERMOSTAT_CONTEXT_K=120      # also fire above 120K context

optional: skill symlinks# Enable /thermostat and /thermostat-week slash commands.
mkdir -p ~/.claude/skills
ln -s /abs/path/to/claude-thermostat/skills/thermostat.md \
       ~/.claude/skills/thermostat.md
ln -s /abs/path/to/claude-thermostat/skills/thermostat-week.md \
       ~/.claude/skills/thermostat-week.md
§ 07 · controls

Every dial, labeled.

Env var Default Meaning
CLAUDE_THERMOSTAT_COST_CENTS 5000 Cost setpoint, in US cents. The only trigger enabled by default.
CLAUDE_THERMOSTAT_TIME_SEC 0 Wall-clock session-age trigger, in seconds. Zero disables; computed and shown in the alert header regardless.
CLAUDE_THERMOSTAT_TURNS 0 Turn-count trigger. Zero disables.
CLAUDE_THERMOSTAT_CONTEXT_K 0 Last-turn input-context trigger, in thousands of tokens. Useful for getting nudged toward /compact before each new turn pays for the whole window.
CLAUDE_THERMOSTAT_CACHE_HIT_MIN 0 Cache-hit-% setpoint; fires when the session cache hit rate falls below this value (requires ≥3 turns). 0 disables.
CLAUDE_THERMOSTAT_COOLDOWN_TURNS 10 Hysteresis. After firing, the alert won't fire again for this many turns of actual work.
CLAUDE_THERMOSTAT_ANTIPATTERNS 1 Antipattern detection (re-Read loops, repeat Bash, exploration grep-chains, subagent over-spawn, MCP result accumulation, inline deterministic work). Set to 0 to disable.
CLAUDE_THERMOSTAT_COST_MODE api api includes cache_read at 0.1x input. subscription (alias: claude-code) is the preferred name; it excludes cache_read, matching the cost Claude Code shows in its statusline (the right choice on Max, Pro, Team, or Enterprise plans). See the design note for the rationale.
CLAUDE_THERMOSTAT_WINDOW_SEC 18000 Length of the rolling subscription window, in seconds. Default is 5h.
CLAUDE_THERMOSTAT_WINDOW_TOKENS 0 Token setpoint summed across the rolling window. 0 disables the subscription-style trigger and hides the tok/5h segment from the header. See the design note below for what this can and can’t tell you.
CLAUDE_THERMOSTAT_WINDOW_COUNT_CACHED 1 Whether cache_read_input_tokens count toward the window sum. 1 weights them at 1.0x (conservative), 0 excludes them.
CLAUDE_THERMOSTAT_AUTODELEGATE_K 0 Context threshold (K tokens) at which the nag instructs Claude to automatically route the next exploration task to a subagent, rather than just offering it as an option. 0 = soft suggestion only. Recommended: 150.
CLAUDE_THERMOSTAT_SUBAGENT_MODEL Model ID passed to the auto-delegate instruction (e.g. claude-haiku-4-5-20251001). Empty = no model hint; Claude uses the session default. Set to a cheaper model to cut subagent cost — e.g. claude-haiku-4-5 is 5× cheaper than Opus.
CLAUDE_THERMOSTAT_CONFIG ~/.claude/thermostat/config.env Path to the optional config file sourced at hook start. Set to /dev/null to skip it.
§ 08 · design notes

Why it works this way.

Why dedupe by message.id

Claude Code re-appends the same assistant message on every tool round-trip — same msg_xxx id, same usage block. Billing-correct accounting treats each unique message.id as one API call. So we dedupe before summing input, cache-write, and output. Cache-read is billed per request, so it sums across the unique calls.

Why Stop + exit 2

The Stop event fires after Claude finishes a response. An exit code of 2 re-activates Claude with your stderr as a system message, so the alert appears as Claude's next response asking the user what to do. The user sees cost stats and can immediately type /compact or /clear.

Why cooldown in turns, not seconds

A session might idle for hours, then resume. Measuring hysteresis by turns of actual work ensures the alert reappears after something meaningful has happened — not just time passing while the laptop slept.

Why transcript parsing

Time and turn count are proxies for cost. Real token counts from the transcript give an actual cost estimate and — more importantly — the context window size, which is what determines whether /compact will meaningfully reduce future turns.

Why there are two cost modes

Anthropic's API rate card bills cache_read_input_tokens at 0.1x the input rate. Claude Code's statusline reports a cost number that excludes cache_read entirely. For a cache-heavy session the two numbers can disagree by 2 or 3x.

Neither is wrong; they answer different questions. api mode tells you what raw API usage would cost on pay-as-you-go billing. subscription mode (alias: claude-code, kept for backwards compatibility) produces an API-equivalent estimate — not the actual quota metric Anthropic uses. Subscription users aren't billed per-token, and the subscription quota formula isn't documented; the figure is useful for orientation and comparison with the statusline, but for real quota tracking use CLAUDE_THERMOSTAT_WINDOW_TOKENS. The default is api because under-reporting cost to an API user is financially harmful, while over-reporting to a subscription user is only confusing. Subscription users opt into subscription mode explicitly.

Why the subscription window is an approximation

Cost setpoints map cleanly to API billing. Max and Pro plans gate on token quotas inside rolling windows, and that counter lives server-side. /usage in the CLI fetches it; no local file holds it.

When CLAUDE_THERMOSTAT_WINDOW_TOKENS is set, the hook builds a local stand-in by summing tokens across every transcript under ~/.claude/projects/ within the window. It’s useful as a gut-check and as a calibration target once you’ve hit a real /usage cap. It is not your real quota. Anthropic’s bucketing isn’t documented, and only Claude Code traffic is visible. Treat the number as directional.