Going from a $700/month student side hustle to a real business in six months came down to one thing: I stopped instructing Claude and started letting it run the whole environment autonomously.
That environment then spent 52 days writing 2,340 log rows where every single cost was zero — and it never once complained.
Why This Setup Works Most people who start with Claude Code use it as a convenient chat AI.
But once monthly revenue crosses a certain threshold, your thinking shifts.
Instead of "issuing instructions and getting output," you move to "letting the whole environment run itself." Here's the concrete difference.
In the first mode, you type a prompt every time and get a result back.
In the second, hooks fire while you sleep, scripts execute, and logs accumulate.
In my case, there are a dozen-odd jobs running on a schedule via launchd, and a Claude Code Stop hook that fires at the end of every session.
I wake up to yesterday's brief sitting on my Desktop, and a record in of how many tokens each session consumed — that was the ideal, anyway.
Why track cost at all?
Claude Code's MAX plan is a flat monthly fee, but there's an intuitive ceiling where "using too much effectively chokes next month's capacity." Without visibility into which session used which model and how much, you're running autonomous agents with zero cost awareness.
The more convenient an autonomous environment gets, the more it silently eats.
That's why measurement comes first.
The Stop hook is the mechanism that handles this measurement.
When a Claude Code session ends (when the user runs , or on timeout), it runs the commands registered in the section of .
Put a cost-aggregation script there and you get a "session ends = automatically recorded" pipeline.
No more hand-typing costs into a spreadsheet. "It's running" and "it's running correctly" are different things — any engineer knows the feeling.
Logs streaming out with all-zero contents is worse than an error.
Errors are easy to notice; a log full of zeros keeps up the appearance of "recording normally" while being completely hollow inside.
I missed it for 52 days and 2,340 rows.
The root cause of cost aggregation "quietly lying" was one simple fact: the Stop hook's stdin does not contain or fields.
The first version was written on the assumption that "the Stop hook's stdin must contain token counts." The Overall Flow Getting a picture of the pipeline the current (v2) runs makes the rest of this easier to follow.
The key point is the design principle: "the only trustworthy data source in a Stop hook is the transcript file." Rather than expecting to arrive directly in the stdin payload, you read the JSONL file Claude Code keeps writing throughout the session yourself.
That is the core of the switch to v2.
Per-Model Rate Table The actual rates are defined on lines 34–38 of the file.
Three tiers: Haiku, Sonnet, and Opus. dispatches based on whether the model name string contains or , and treats anything else as Sonnet.
A simple string match is plenty here; this doesn't warrant a regex.
Caching matters more in cost calculation than you'd expect. (the initial cache write) is 1.25x normal input, and (reads from the second time on) is 0.1x.
If you use caching heavily with Opus, the read rate works out to $1.50 per million tokens — which can be cheaper than Haiku's normal input.
The Function That Scans the Transcript Lines 57–90 are the heart of this fix.
It parses JSON line by line, picks out only entries where , and accumulates .
For the model name it uses the last one found (the assumption being that even if the model switches mid-session, the final assistant turn carries the correct model).
Error handling is deliberately loose.
If the file can't be read, ; if a line can't be parsed, .
In both cases the goal is to avoid failing the Stop hook as a whole.
A missing cost record is less of a problem than the Stop hook throwing an error and affecting Claude Code's session termination.
Reading stdin and the Full Fl