Agent setup: authorize Sight MCP and ask your agent to call
get_tracking_setup. It configures this integration automatically.
Claude Code spend hook
A Claude Code Stop hook that tracks what you spend running Claude Code
to build your product, and reports it to
Sight — where it shows up
next to your revenue (AgentSpendCard on the dashboard, get_agent_spend
via MCP). This is the primary, automatic path for Sight's "agent
build-spend" feature; logging an entry by hand via the track_agent_spend
MCP tool is the fallback for corrections.
Part of Sight, split across repos under the sightapp org — see
sightapp/sight-backend's
SPEC.md for the full picture.
How this works
Claude Code's Stop hook fires once per turn, but its payload does
not include a ready-made cost figure — only (per the current docs)
session_id, transcript_path, cwd, and sometimes a coarse
tokens.input/tokens.output. There is no claude cost/usage CLI
command to shell out to either. So this hook:
- Reads the turn's real token usage (input/output/cache) and model from
transcript_path(a JSONL file Claude Code writes as the session progresses — falls back to the Stop payload's coarsertokens.input/tokens.outputif the transcript hasn't caught up yet, which can happen since it's written asynchronously). - Computes a cost estimate itself, using a local pricing table
(
src/pricing.js) — Anthropic doesn't hand us a dollar figure, so we derive one from tokens × known per-model rates. Ships with current rates; override without a code change by dropping a~/.sight/pricing.jsonwith the same shape. - Accumulates fractional cents across turns rather than rounding
each one — a single small turn is often well under a cent, and since
Stopfires every turn, rounding-to-zero per turn would silently drop real spend for the common case. The leftover fraction persists in~/.sight/remainder.jsonand carries forward until it crosses a whole cent. - Buffers locally, batches the network call — appends each turn's
entry to
~/.sight/agent-spend.buffer.jsonland only POSTs to Sight once the buffer hitsflushMaxEntries(default 20) orflushMaxAgeMinutes(default 15), whichever comes first. This keeps a hook that fires every single turn from making a network call every single turn. The flush itself runs inside theStophook (10-minute timeout budget) rather thanSessionEnd(a much tighter ~1.5s shared budget across allSessionEndhooks) —Stopis the only place with enough headroom for a reliable POST. - On flush failure (network error, non-2xx), the buffer is left intact
and retried on the next
Stop— nothing is dropped on a transient failure.
Never blocks or fails the Claude Code session: every code path exits 0,
errors go to stderr (never stdout — Claude Code treats non-JSON stdout
from a Stop hook as text appended to its context, so this hook stays
silent on success).
Install
No npm install needed to run it — the hook script itself has zero
dependencies (only Node built-ins), specifically so it works reliably
inside whatever environment Claude Code invokes hooks in.
1. Configure
Create ~/.sight/config.json:
(Or set SIGHT_API_KEY env var instead — it takes precedence over the
config file.)
2. Register the hook
Add to ~/.claude/settings.json (applies to every project) or a specific
project's .claude/settings.json:
Verify this against the current Claude Code hooks docs before relying on it — the payload shape and cost-derivation approach above are confirmed against current docs, but the exact
settings.jsonregistration schema (matcher requirements, env var interpolation like$CLAUDE_PROJECT_DIR, timeout field support) wasn't independently re-verified in the session this hook was built in. If the schema has moved, this is the one place to double-check.
Configuration reference
~/.sight/config.json:
~/.sight/pricing.json (optional, overrides src/pricing.js's defaults):
Local files this hook manages
~/.sight/agent-spend.buffer.jsonl— unflushed entries.~/.sight/remainder.json— fractional-cent carry-over.~/.sight/config.json/~/.sight/pricing.json— you create these.
None of these are Claude Code's own files — safe to delete any of them to reset local state (you'll lose unflushed spend for the current buffering window, not historical data already sent to Sight).