01 — transcript-audit

I had six months of AI coding sessions on disk and no idea what was in them.

Every session you run with Claude Code or Codex writes a transcript to your machine. Mine came to 2,094 files and 2.1 GB. That is the most accurate record that exists of how I actually work: what I ask for, how I phrase it, where I lose hours, which corrections I have typed forty times without ever writing down as a rule.

It is also completely unreadable. Too big for me to sit and read, and far too big to paste into a model. So it sits there, accumulating, being the answer to a question nobody can ask.

transcript-audit is the thing I built to ask it. It points an agent at all of it and hands back a report where every finding carries a receipt from my own history, and every fix has an approve button. It is open source, MIT, and it runs entirely on your machine.

The whole thing is one Python file with no dependencies and a skill that drives it. Nothing leaves your computer.

02 — The one design decision

Arithmetic on one side, judgement on the other.

Everything else in this project follows from a single line drawn down the middle of it.

Anything countable, groupable, sortable or extractable happens in Python, deterministically, with no model involved. Anything requiring taste happens in a model, reading only small shaped windows that the Python produced. If I am adding something, the first question is which side of the line it goes on.

That is not a performance optimisation. It is the thing that makes the output specific instead of generic. A model that has read a fair sample of six months says different things than one that has read last Tuesday, and a model that never sees raw transcript text cannot quietly start pattern-matching on the last thing it read.

There is a hard rule attached: no transcript text ever enters a model's context except through a bounded read. Not rarely. Never. The moment you let an agent cat a transcript "just to see", every judgement it makes afterwards is polluted by material it did not choose, and you have rebuilt the problem you were trying to solve.

03 — The deterministic layer

One file, 1,800 lines, standard library only.

The install is a copy. There is no pip, no package, no virtualenv, no network call anywhere in it. That is deliberate: this thing reads your entire private working history, so you should be able to audit the one file that touches it before you run it. A dependency tree makes that impossible in practice.

It reads both Claude Code (~/.claude) and Codex (~/.codex) history, normalises both onto one small dict, and makes a single streaming pass over the corpus. On mine that is 2.1 GB in about sixteen seconds. It writes ten JSONL slices to disk, each one a different question about the same history:

promptsevery instruction you typed, in order
correctionsthe turns where you redirected the agent, and what it had just done
interruptswhere you cut it off, and what was running
commandsevery shell command, normalised to its shape
tool_errorswhat failed, with the target
dispatchesthe subagent packets you sent
invocationsskills and slash commands, when, where
reworkfiles edited repeatedly inside one session
sessionsone row each: shape, cost, duration, strain
praisethe approvals. The weakest slice, and I say so in the docs

Then there are the aggregates, which are free and where most of the real findings actually come from. Cost by month, tools by error rate, the hours you work, what every session pays to start before you type a word, and the command shapes you have retyped by hand. On my corpus the arithmetic alone found that I had typed PYTHONPATH=subsystems in front of commands 1,010 times because one repo had no packaging file, and rebuilt the same eleven-line remote environment preamble across 1,240 ssh calls.

Neither of those needed a language model to notice. They needed someone to run the counts and look.

$ transcript-audit scan
  scanned 2094 files (2134 MB) in 16s

$ transcript-audit stats --what shape
bucket                 sessions   share  human turns    tools
-------------------------------------------------------------
did work (>=5 tools)       1271   61.0%         4589    67206
thin (1-4 tools)            122    5.9%          145      316
no tool calls at all        691   33.2%          710        0

! 691 session records made zero tool calls (33% of the corpus).
  They are in every per-session denominator unless you exclude them.

That last warning exists because it caught me. A third of what called itself a session in my corpus did no work at all, and one of my findings inverted completely once I filtered them out.

04 — The nine seats

Nine ways of being interested in the same history.

Once the Python has done the counting, the skill seats a set of independent subagents on different corners of the evidence. Each one is a person with an appetite, not a checklist. That distinction matters more than it sounds: a seat that wants something finds things that look bright to it and goes past everything else, and two seats reading the same slice come back with genuinely different findings.

So I do not tell a seat what to find. It gets the appetite, the material, and the shape of the return. The ideas are its own. If I specify the conclusions I have written a search query with extra steps, and I will get back exactly what I already believed, dressed up as discovery.

The economist Where the time and the tokens went, and what came back for them. Has seen a lot of expensive sessions and has stopped being impressed by activity. Allergic to averages that hide the tail.
The instruction reader Reads my prompts the way an editor reads a draft: not for what I meant, but for what was on the page and what a stranger would have built from it. Hunting for the correction I have typed more than once.
The cartographer of friction Finds repetition physically uncomfortable. Thinks in terms of what a machine should have been doing while a human did it by hand. Refuses to automate anything that ran twice.
The historian Bored by any single moment, only comes alive comparing two. What do I do now that I did not do in month one, what did I pick up and drop, what has refused to move.
The salvor Treats every closed session as a shipwreck and goes down looking for cargo. After the thing that was worked out once, at cost, and written nowhere durable.
The inventory The distance between the machine as configured and the machine as used. Its best output is usually subtraction: the skill never invoked, the rule the transcripts show being ignored.
The coroner Reads the worst sessions as timelines, without sentiment. Only interested in a death it has seen more than once, because one bad session is weather.
The witness Every other seat reads me. This one reads the other side of the conversation: what my agents claimed, versus what the record shows happened next.
The falsifier Runs last, alone, and never sees how the findings were made. It assumes the draft is flattering and padded, because drafts usually are, and its job is to kill things. Its judgement is final on cuts.

The coroner is the one I argued about most while building it. The first version was a full session-autopsy tool, and I killed it. If a session went badly, the user was there. The frustration is already encoded in their memory and they do not need an AI to parrot it back at them. What they cannot see from inside any one session is recurrence: the same death happening for the fourth time across three projects. So the seat only ships a finding if the failure class repeats.

05 — What comes out

A document, not a dashboard.

The report is one self-contained HTML file with no external requests. I designed it as a forensic exhibit list rather than a set of analytics cards, and there is one idea running through the type: judgement sets in a serif, evidence sets in monospace. The two faces are the two halves of the system. A model wrote the sentence in serif; arithmetic produced the line in mono. You can tell at a glance which one you are reading.

The top of a transcript-audit report, showing the title, the corpus summary, five counted statistics and the opening of the verdict.
The masthead and the verdict. The numbers are counted, never estimated: the tool reports tokens as tokens and refuses to invent dollar figures unless you hand it a rates file.

Every finding is an exhibit. The rail on the left carries the filing metadata, the claim sits in one measure of prose, and the receipts are always visible underneath it. That last part is not a style choice. The entire claim of this tool is that a finding carries evidence, so hiding the evidence behind a disclosure triangle would argue the opposite of the product.

A single finding in the report: title, claim, three pieces of quoted evidence with session ids, a proposed remedy with the literal text to apply, and approve and skip controls. The finding is stamped approved.
One exhibit, approved. The remedy carries the literal text that gets applied, not a description of it. The stamp is the only colour the document is allowed, and it means exactly one thing: you decided this.

You approve what you want, skip the rest, and hit copy. That produces a small JSON blob you paste back to the agent, and one follow-up message edits the CLAUDE.md, installs the hook, writes the script. Everything you skip is dropped silently. No follow-up, no nagging.

The same finding rendered in dark mode.
Dark mode is a real inversion, not an afterthought.
The report on a narrow mobile viewport, with the statistics stacked into a single column.
Narrow viewport. The rail folds, the measure holds.

There is a validator in front of the renderer, and it is the most important twenty lines in the file. It refuses to write a report from a finding with no evidence, no source on that evidence, or no literal payload on something actionable. The model only ever produces the findings JSON; Python renders the HTML. That is what makes the gate enforceable rather than aspirational, and it means the tool mechanically cannot hand you the "be more specific in your prompts" blog post.

06 — Running it on myself

The first real run found five false things, and that is the interesting part.

I built the thing, then ran it end to end on my own 2,094 transcripts and made the agent face me with the result. Twenty-two findings reached the draft. The falsifier cut five outright, weakened thirteen, and passed four clean. I approved six of the nine actionable items.

What I did not expect was how the false ones failed. Almost all of them were the instrument lying, not the model hallucinating:

Every one of those is now fixed in the tool rather than papered over in the prompt. Clipped fields carry their true length beside them. There is a stats --what shape that counts the dead records and prints every rate per-session and per-human-turn, because those two numbers disagreeing is the tell. And the seats are told, in the packet, that a lexical grep cannot establish absence.

The correction I liked most was one I made myself. The report told me I was paying a 43,000 token agent context to make what was functionally a single API call, 676 times. That was true, but its proposed fix was wrong: routing those through the CLI is deliberate, because it bills my subscription rather than an API balance, and the subscription is heavily subsidised. The waste was never the route. It was that each of those calls was loading a full CLAUDE.md, skills, hooks and MCP schemas to run a stateless extraction prompt. Stripping that took four flags and measured out at 30,289 tokens down to 6,618, a 78% cut, with the billing unchanged.

A checkbox could not have told me that. Which is worth remembering if you are building a tool that asks someone to approve things.

07 — What it does not do

Stated plainly, because every tool has these and most pages hide them.

08 — Get it

MIT. Python 3.9+. Nothing else.

git clone https://github.com/JustOscarJ1/transcript-audit
cd transcript-audit
./install.sh          # or: powershell -File install.ps1

Then /transcript-audit in Claude Code. The scan takes a minute or two, the seats read their windows in parallel, and the report opens in your browser.

The Python layer also works entirely on its own if you just want the numbers and none of the agents:

python tools/transcript_audit.py stats --what costs       # tokens by month, cache hit rate
python tools/transcript_audit.py stats --what overhead   # what your sessions pay to start
python tools/transcript_audit.py stats --what automation # commands you keep retyping
python tools/transcript_audit.py stats --what shape      # read this before dividing by anything

github.com/JustOscarJ1/transcript-audit