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.
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.
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.
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.
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.
/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
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.
/compact, one /clear on task pivot./model sonnet for routine work.docs/pricing-spec.md 5× — convert to a skill at ~/.claude/skills/ so it loads on demand rather than re-reading every turn.codegraph_explore for natural-language lookups; one call usually replaces a chain./compact <what to keep>./effort or MAX_THINKING_TOKENS.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./context to see what each costs, /mcp to disable idle ones, and prefer CLI tools (gh, aws) — they add no per-tool listing.node_modules and dist. Add them to .claudeignore so they stop burning context.
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.
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.
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.
Put it anywhere on disk you'd keep a personal hook. You only need the shell scripts and the python helper — no install step.
settings.jsonOpen ~/.claude/settings.json and register the Stop and SessionEnd hooks. Restart any open Claude Code sessions for them to take effect.
Drop a config file at ~/.claude/thermostat/config.env, or set CLAUDE_THERMOSTAT_COST_CENTS in your shell. Default is $50.
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
| 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. |
message.idClaude 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.
Stop + exit 2The 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.
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.
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.
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.
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.