Git history tells you what changed. It does not tell you that you tried three other approaches first, that the second one failed for a reason you’d never guess, or that the fix you shipped was the fourth idea and the first three are all still bad ideas today.
That reasoning happens in the session where the work happens. Then the terminal closes and it’s gone.
Except it isn’t gone, exactly. Every Claude Code session writes a full transcript to disk as JSONL. When I went looking, I had 463 of them — 355 MB of conversational history with no search surface at all. Six months of decisions in a format nobody would ever read.
So I wrote a pipeline that reads them and files the useful ones in my notes.
The shape of it
read JSONL → strip non-text → redact secrets → grade → summarize (Haiku) → markdown
One Python script, one dependency, output as dated markdown in my Obsidian vault tagged #worklog. Today that’s 315 notes spanning April to August — 244 graded high signal, 65 medium, 6 low.
The extraction step is more opinionated than it sounds. Tool calls get compacted to a single line with their input truncated to 240 characters. Tool results get truncated to 600. And thinking blocks get dropped entirely, with a comment explaining why:
elif t == "thinking":
# Drop thinking blocks — they're encrypted/signed model state, not useful for summary
continue
What survives is user turns, assistant prose, and a compact ledger of what tools ran on what files. That’s the raw material.
Signal grading, and the reason it exists
The summarizer returns a fixed JSON shape — title, summary, decisions, learnings, commands, files, tags, and a signal grade. The grading rubric is three lines in the system prompt:
high — concrete deliverable, decision logged, or persistent infra change medium — research/scoping/planning, useful context low — debugging dead-ends, abandoned thread, off-topic chat
The automated path only writes medium and up, and the reason is more interesting than “avoid clutter.” These notes get embedded into a personal retrieval system, and that system has a ranking pathology where tiny pages punch above their weight. A one-paragraph note about a session where I fixed a typo doesn’t just add noise — it actively outranks the real answer. Low-signal notes made retrieval measurably worse.
So the gate isn’t tidiness. It’s protecting the index.
The redaction ladder, and the leak that taught me the top of it
Everything gets scrubbed before the summarizer sees it: API key formats, OAuth tokens, JWTs, AWS keys, bearer headers, private LAN and mesh-network IP ranges, personal phone and email patterns, plus a defense-in-depth list of specific known credentials. Each hit is counted and the counts get published in the note itself, so I can see what was caught.
That’s the easy half. The other half I got wrong.
Certain topics — finance, tax, anything touching a client’s credentials — aren’t supposed to be summarized at all. My first version filtered by working directory, which failed immediately, because most of my work starts from my home directory rather than inside a neatly-named project folder. So I added content-level redaction and reprocessed.
Then a published note described an excluded topic anyway. The text had been scrubbed correctly. The model had reconstructed the subject from the file paths in the metadata — the files_touched list I was helpfully including for context.
The fix was to stop trying to sanitize those sessions at all:
# Hard kill on confidential-topic detection. Don't summarize, don't publish.
confidential_hit = {k: v for k, v in counts.items() if k in CONFIDENTIAL_LABELS}
if confidential_hit:
return None # treat same as opt-out — no markdown written
If a confidential marker appears anywhere, the session is dropped before the API call. The model never sees it. Surviving sessions additionally get confidential path fragments stripped from their file lists.
The general lesson, which cost me two full reprocessing runs: redaction that leaves metadata intact isn’t redaction. A capable model will happily reassemble what you removed from what you left behind.
There’s also a blunt ratio guard — if scrubbing removed more than 40% of the text, the session is written off as too dirty to summarize. In 315 notes it has never once fired, which either means my regexes are well-targeted or that the threshold is too generous. I genuinely don’t know which.
Provenance is the part I’d never remove
Every note ends with a block like this:
## Provenance
- Source: `~/.claude/projects/<slug>/<uuid>.jsonl`
- Bytes read: 398,163
- Tools: Bash:20, Edit:3, Read:2, ToolSearch:2, Skill:1, Write:1
- First user prompt: _...
- Redactions: {'PRIVATE_IP_TS': 4}
These notes are model-generated, which means they can be wrong. The provenance block is what makes them auditable: it points at the exact transcript, so any claim can be checked against the source. The tool histogram is a surprisingly good fingerprint too — twenty Bash calls and three edits is a debugging session; thirty edits and two Bash calls is a build.
Frontmatter carries date, title, session UUID, project, model, duration, event count, signal, tags, and the files touched. All queryable, all sortable, all searchable from the same ⌘K box as the rest of my notes.
Then I stopped running it by hand
For the first six weeks this was manual, governed by one rule I’d learned the expensive way: always preview to /tmp first, never write straight to the vault. The eyeball gate is the only reason I caught the metadata leak.
Now it’s automatic. A SessionEnd hook fires when a session closes, skips anything under 32 KB (too small to be worth a summary), detaches, and runs the pipeline with a medium signal floor. A nightly sweep catches what the hook misses — sessions that ended in a crash, a power loss, or a Cmd+Q — and re-processes any transcript that grew after its note was written.
The preview-first rule is formally superseded on the automated path. The gates replaced the eyeball: opt-out, then redaction, then confidential hard-kill, then the signal floor. I only preview manually now for bulk backfills.
What it costs
The initial backfill was 131 in-scope sessions producing 104 notes, at about $1.50 in API spend. I then paid that twice more reprocessing after the two redaction failures, so call it $4.50 all in. Ongoing is roughly $3 a month.
There’s a fourth $3 that I don’t count as well spent: my --skip-existing flag originally called the summarizer before checking whether the note already existed. Which is exactly as useless as it sounds.
What doesn’t work
Seven sessions from the original backfill are simply gone — their transcripts were compacted mid-run and the files vanished underneath me. There’s no recovering those.
The “first user prompt” field is meant to be a one-line reminder of what the session was about, and it’s frequently useless. A pasted screenshot renders as [Image #1] [IMAGE attached], which tells me nothing at all. A decent chunk of my sessions start with a screenshot.
And the whole thing is downstream of a model’s judgment about what mattered. The signal grades are good but not perfect, and a session graded low is silently never written. I have no idea what’s in the gap, by construction.
The payoff, honestly
Here’s the test I didn’t design but got anyway.
Most of the systems posts on this blog were written months after the work happened. Not from memory — I don’t have that kind of memory — but reconstructed from these notes. The post about my Cloudflare tunnel being dead for three days came out of a note the pipeline wrote automatically the day I fixed it: the container stuck in Created state, the network name that no longer existed, the specific clue that cracked it, the monitor I added afterward so it couldn’t happen silently again.
I would have remembered none of that. I’d have remembered “the tunnel broke once.”
A year of work turned out to be recoverable in specific, quotable detail, because a cheap model wrote it down every night while I wasn’t paying attention. That’s about three dollars a month for an engineering memory that doesn’t decay.
It may be the best value in my entire stack, and I almost didn’t build it because reading my own transcripts felt like navel-gazing. I’m glad I stayed curious long enough to try. The transcripts turned out to be the only place my reasoning was written down.