Three answers, and one of them is
“I can’t tell you”
Most checkers have two states: fine, or broken. That grammar lies the moment evidence goes missing, because “nothing wrong here” and “nothing here to check with” come out looking identical. routeledger keeps them apart.
- ok
- A model was declared, a model answered, and they are the same family. The only case where the tool is entitled to say nothing is wrong.
- observation
- Something changed and the transcript does not record why. Reported as a hypothesis, with the reason it cannot be more than one. A mid-session settings edit and a resume leave no trace in the file. Absence of a cause in the record is not absence of a cause.
- unverifiable
- The evidence needed is not in the file. No verdict is issued, and none is implied. This is a real answer, not a failure to produce one. It appears often — nine times in the session above.
An auditor that is confidently wrong is worse than no auditor.
What it reads
Three checks, run against files already sitting under ~/.claude/projects. Each one states its own limit, because a limit you have to discover later is a defect.
Declared model versus served model
meta.json declares no model, there is nothing to compare against — so the finding is unverifiable, not “fine”.[UNVERIFIABLE] agent-a90c8e85777de7dde (general-purpose) no declared model; served: claude-fable-5, claude-opus-4-8
Every subagent run leaves a record of the model it was asked for, next to a transcript of the model that answered. routeledger joins the two. That one run was served by two different models — a fact that appears nowhere else.
Model changes inside a session
[OBSERVATION ] unexplained model change claude-fable-5 -> claude-opus-4-8 @ 2026-07-19T15:32:10.329Z inference: no user action found; but a mid-session settings.json edit and a resume are not recorded in the transcript, so this is a hypothesis
It walks the served model across the session and separates the changes you asked for from the changes you did not, by looking for a preceding /model command.
Plan-mode profile
[OBSERVATION ] plan-mode profile plan: claude-fable-5 | execution: claude-fable-5, claude-opus-4-8 inference: claude-fable-5 served turns on both sides of the plan-mode boundary; if you expected a plan-mode upgrade, it may never have fired. […]
Which models served plan-mode turns, and which served the rest. The two sets are not identical here, but they overlap — Fable 5 answered on both sides, so the plan-mode boundary did not separate the models at all. A checker comparing the sets for equality would call that fine. This one does not.
Output above is verbatim, from the session drawn at the top of this page. Long lines are wrapped to fit the column, and one trailing sentence is elided at […]; nothing else is changed.
It stops asserting on a version it has not seen
The Claude Code transcript format is undocumented and it moves. While building this, a single transcript turned up spanning five CLI versions, and a record type present in one session was simply absent in another.
So routeledger records the version behind every session it reads. On a version newer than the last one it was verified against — or when no version was recorded at all — it withdraws. Every finding drops to unverifiable, the claim it would have made is kept visible in the detail line, and the models-served table is relabelled a raw, unverified count.
! This session is newer than the version routeledger
was verified on (2.1.220).
Findings are treated as unverifiable; the format
may have changed.
MODELS SERVED (raw count — format unverified)
claude-opus-5 1 turn
claude-fable-5 1 turn
FINDINGS
[UNVERIFIABLE] unexplained model change
claude-opus-5 -> claude-fable-5
(version fence: unverified version,
no "observation" claim made)
Real output, from the test fixture that holds this path open: a transcript recording Claude Code 9.9.9. The withdrawn word stays in the report, so you can see exactly which claim was not made.
Absence of evidence closes the fence too. If no version was recorded, routeledger does not assume it is safe — it assumes it cannot tell.
What it will not do
- no networkEver. There is nothing to configure and nothing to opt out of.
- writes nothingNo files, no cache, no telemetry. Read-only by construction.
- changes nothingIt cannot switch models or touch your configuration.
- reports no contentYour prompts and Claude’s replies are never printed, stored, or transmitted.
One narrow exception, stated here because “it does not read your content” would be false. To tell a model change you requested from one you did not, the second check scans user text in memory for the /model command marker.
That text never enters a finding and never leaves the process. The scanned value is passed to one regular expression and discarded.
Running it
Node 20 or newer, and transcripts under ~/.claude/projects. No install, no configuration, no account.
routeledger
Audits the most recent session for the current directory, falling back to the newest session anywhere — and saying which one it picked.
routeledger <session-id>
Audits a specific session. A prefix is enough.
routeledger --sessions
Lists recent sessions, newest first, so you can pick an id.
routeledger --json
The same report as machine-readable JSON, always with every finding included.
Nearby, and honestly
-
ccusage
What your sessions cost, broken down by model. A different question, and the one most people need first.
-
fableplan
Sets up a Fable-plans, Opus-executes routing. routeledger checks whether the routing you set up is the one you got.
Findings describe one session. There is no history, no policy file, and no CI-gate mode in this version — a finding never changes the exit code.