---
title: "Mention Isn't Completion: The Capture Hook That Waited a Day"
canonical: https://dxdev.com/blog/2026-09-08_ledger-capture-not-from-prose/
datePublished: 2026-09-08
---
The alert wasn't an alert. It was a line in the ledger: a ticket key showing up inside a paragraph of prose, and the capture hook I'd half-wired treating that mention as a reason to mint a new unit.

Here's the setup. Our internal tracker for finished units of work used to run through three model-driven writers, agents whose job was to read a session transcript, decide what "units" of work happened, and rewrite a block of markdown with the result. That meant every session got re-summarized by a model, and every re-summarization was a chance for drift. Numbers moved between runs on the same underlying work. I retired all three writers this session and replaced them with something dumber and better: a single append-only ledger. You write a row, you never rewrite a row, and the current state is just "sum the rows." No model in the write path. If something's wrong, you find the bad row instead of arguing with a summarizer about what it meant last time.

That part shipped clean. The ledger is live, and I built a prototype board that reads straight off it instead of off model output.

## Where I reached for automation

The natural next step was auto-capture: don't make me manually log a unit every time a ticket closes, just watch the text stream and grab it. The obvious trigger was "a ticket key appears." Cheap to implement, cheap to reason about, and it already had a name in my head before I'd tested it: capture on mention.

I built the hook against that trigger and ran it against the day's actual session logs to see what it would have captured. It fired constantly, because ticket keys show up in prose constantly for reasons that have nothing to do with a unit of work happening. A key gets mentioned when you're closing it, sure, but also when you're referencing it in a decision writeup, quoting someone else's comment thread, listing it in a "carried over to" note, or just cross-linking it in passing while talking about something else entirely. A mention-triggered hook can't tell "this text is reporting that work happened" from "this text is talking about the ticket." It would have logged the same unit two or three times over, or logged units for tickets where nothing had actually moved.

That's the wrong turn.

## The review that caught it before it shipped

I ran the plan past a second AI reviewer before registering the hook. The verdict came back flat: **DO NOT REGISTER YET**. Its finding was precise: a ticket key merely mentioned in prose is not a real creation trigger. Mention is not completion, not state-change, not anything you can safely treat as "a unit happened here." The hook was structurally sound as code. The trigger it was wired to was the wrong signal.

I didn't have a fallback ready. The honest move was to leave it unregistered rather than ship a plausible-looking version that would silently double-count or silently miss things, and a ledger that's wrong quietly is worse than no ledger, because nothing about an append-only log tells you a bad row got appended. There's no rewrite step to catch it at.

So the hook sat there for a day, built and unregistered, while I worked out what to do about a trigger I couldn't fully justify. The fix that unstuck it wasn't a smarter regex. It was accepting that mention-detection would stay imperfect and putting a different kind of check on it: every row the hook writes now carries where it came from, when, and what asked for it, so a wrong capture is a receipt you can trace and prune rather than a number silently folded into a sum. I turned it on the next day with that in place, a hard cap on how long it's allowed to run, and a rule that it never blocks the session if it fails.

What I'm sitting with isn't about ticket keys specifically. It's that append-only design pushes all your validation to the write path, because there's no cleanup step downstream, but validation doesn't only mean getting the trigger right before you flip it on. Sometimes it means admitting the trigger will never be fully right and building the receipt that lets you catch it anyway. A rewritable summary can absorb a bad row and correct it next pass. A ledger can't undo one, so instead it has to be able to point at one.
