Build1 distinct publisher3 min readPublished
A Claude Code Stop hook appended 2,340 session rows that all said the session cost nothing, because it read fields the payload never carried. Flat-fee billing left no invoice to argue with it.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
A crashing hook gets fixed the same morning, because something you expect stops arriving. This one arrived on time, every time, and said nothing had been spent. At 2,340 rows over 52 days the pipeline produced roughly 45 answers a day [14], and every one of them was wrong in the same direction.
The mechanism is worth being precise about, because it generalises past this one script. Version one was written on the assumption that the Stop hook's stdin carried token counts [6]. What stdin actually carries is session_id, transcript_path, cwd and hook_event_name, with no usage or model field anywhere in it [5]. Reading a field that is not there does not throw; the arithmetic still resolves, the append still succeeds, and the row still lands in ~/.claude/metrics/costs.jsonl [4]. A wrong assumption about a payload schema does not surface as an error. It surfaces as a plausible number.
Nothing in the setup was ever going to argue back. The author's stated reason for measuring at all is that Claude Code's MAX plan is a flat monthly fee with a soft ceiling, where heavy use chokes the following month's capacity [13]. That is exactly a situation with no itemised bill to reconcile against. The metric exists because no invoice enumerates sessions, which is also why a hollow metric survived seven and a half weeks. Self-reported telemetry with no counterparty is the case that most needs an assertion on the value, not just on the exit code, and zero is a legal cost, so absence and genuine idleness are indistinguishable without one.
Version two moves the read to the only thing the hook can trust: the transcript JSONL that Claude Code keeps writing during the session, parsed line by line for assistant turns carrying message.model and message.usage [7]. Input, output and cache token counts are summed across all turns, multiplied by per-model rates and divided by 1e6 [8]. That is a sound repair, and it inherits two soft spots.
The first is dispatch. getRates() looks for the substring 'haiku' or 'opus' in the model name and treats everything else as Sonnet [10]. With Sonnet input at $3.00 per million and Opus at $15.00 [9], a model name that matches neither string gets logged at one fifth of Opus input cost [16]. The default branch is a rate, not a failure, so this too would record quietly.
The second is the cache arithmetic the post uses to make its point. It argues that heavy caching on Opus, at $1.50 per million read tokens, can come out cheaper than Haiku's normal input [12]. The table printed a few lines above puts Haiku input at $0.80 per million [9], which makes the Opus cache read 1.875 times more expensive, not less [15]. The cache multipliers themselves are the real finding: writes at 1.25x input, reads at 0.1x [11], which is where the variance in an autonomous agent's bill actually lives.
Ranked by verification strength, evidence, and original report placement.
The author reports an autonomous Claude Code environment ran for 52 days and wrote 2,340 log rows in which every cost was recorded as zero, without ever raising a complaint or error.
The author frames the change as moving from issuing instructions to Claude and getting output, to letting the whole environment run itself, with about a dozen scheduled jobs under launchd and a Stop hook at the end of every session.
The Claude Code Stop hook fires when a session ends, either on /exit or on timeout, and runs the commands registered in the Stop section of settings.json.
The cost-aggregation script appends one line per session to ~/.claude/metrics/costs.jsonl.
The Stop hook's stdin payload contains session_id, transcript_path, cwd and hook_event_name, and does not contain usage or model fields.
The first version of cost-tracker.js was written on the assumption that the Stop hook's stdin contained token counts.
Follow any of these and your For You feed starts watching them — no settings page required.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Detailed but single self-reported account
The mechanism claims are unusually specific for a single source: the post names the stdin fields present and absent, quotes the parsing function with line numbers, and prints the rate table. But everything rests on one practitioner's own environment, with no vendor documentation, changelog or second implementation confirming that the Stop hook payload omits usage and model, and no output artifact from the corrected pipeline is shown. One quantitative comparison in the post is contradicted by its own table, which lowers confidence in the numeric care applied elsewhere.
One solo environment
The only adoption evidence is the author's own setup: a single flat-fee subscriber running roughly a dozen scheduled jobs and one session-end hook, with a rewritten tracker now in place. No other users, teams, repositories, downloads or organisational deployments are disclosed, and the pattern is not shown to be in use anywhere beyond this one machine.
Framing outruns the verified detail
The core diagnosis is modest and plausible, so this is not a heavily inflated story, but the packaging reaches past the evidence: a side-hustle-to-real-business opener and a generalised 'Stop hook trap' lesson are built on one unverified account, and the pricing argument that Opus cache reads can undercut Haiku input is refuted by the article's own rate table. The naive model-name fallback that would understate Opus-class input fivefold is presented as adequate rather than as a residual accuracy limit, so the fixed pipeline is implied to be more trustworthy than the shown code supports.
Self-published practitioner promotion
This is a self-published developer-platform post whose opening trades on the author's own revenue trajectory, giving a clear reputational and audience-building incentive to dramatise the failure and the recovery. No vendor sponsorship, affiliate relationship or paid disclosure appears, and no commercial product is being sold in the excerpt, which limits the pull. The flat-fee subscription framing also gives the author a self-interested reason to emphasise capacity anxiety that no invoice can validate.
Low: one unreviewed source, one internal contradiction
Confidence is limited by a single-publisher, single-source cluster with a truncated body, no vendor or third-party corroboration of the hook payload contract, and no adoption signal beyond the author's own machine. The specificity of the quoted code and the falsifiability of the 52-day, 2,340-row figure raise it above the floor, while the demonstrable arithmetic contradiction inside the post keeps it well below the midpoint.
build
A Retention Policy for Agent Memory: Flag Unused Skills at 30 Days, Archive at 901 distinct publisher
product
A 2x LLM bill is not a bug report: token spend is an observability problem1 distinct publisher
build
A cost monitor overcounted 4.9x, then went dark for a week when set -e did its job1 distinct publisher
build
Thirteen tasks green, then "give up (Recommended)" on the one that needed understanding1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 24, 2026