Every gate in my system is a scar
Last week a Claude Code session told me my order queue had 180 open items, that none was older than 13 days, and that 221 of the 254 entries had been written by Claude Code sessions. It had counted itself. The biggest single mailbox in the system was the system working on itself.
I had not looked at that list in a while.
Ten projects and one Markdown file
I started working across several projects with Claude Code at the end of last year, and back then the whole system was a TODO.md per project folder. I opened a session, said what needed doing, the file kept the rest. For two or three projects that is completely fine and I would recommend it.
Then it became ten, and three things went wrong at once.
Ideas got lost on the way home, because the place to write them down was a Markdown file on a machine I was not sitting at. Mornings turned into a decision I did not want to make: ten lists, no shared view, no ranking across them, so the project that got my attention was usually whichever one I had touched last. Which is the opposite of a priority.
And projects went quiet for weeks. Not because they were finished. Because nothing in my setup ever raised its hand, and a file does not tell you it has been ignored.
What it looks like now
Last week I wrote the manual, which I had been putting off. Not documentation of the code, a manual for the operator, and the operator is me. Where I do what, which commands exist, how a note becomes an order, which four places stop and wait for a human. Eight sections. A tool that needs a manual for its only user is telling you something about how it grew.
Anything that could become work goes into one collection point, and from there it has exactly one path.
Two clockworks run behind this, and neither of them thinks. A launch agent empties the drop box every 15 minutes (StartInterval 900), sorting entries into the right project’s list. A second one every 30 minutes (StartInterval 1800) picks up approved orders and runs them headless in a git worktree under ~/.cache/master-dispatch-worktrees, deliberately outside every repository. No model is involved in the sorting step, which is why it is free to run it that often.
The part I use most is the least clever one. I send myself an email.
That is the fix for the first of my three problems. The thing I can always reach is my own inbox, so that became the entry point. What it does not do is turn a note into work. It becomes an order and then it stops, which brings me to the reason any of this has gates at all.
Three sessions, one file
On 17 July three parallel sessions each read the same shared config file, changed their own line, and wrote the whole thing back. Last writer won, one session’s exception silently erased. I wrote that one up separately, so the short version here: shared resources are only writable through a lock now, and a hook refuses any command that tries to skip it.
The realisation behind it is the part that stuck. I had been treating parallel sessions as if they were parallel humans, who would notice. They do not notice. They are processes, and the moment two of them share state you owe them the discipline you would owe two threads.
The tool that produced more work than it finished
Which brings me back to the count at the top.
By 28 July the queue held 180 open orders and none of them was older than 13 days. That means the system had produced roughly 20 new orders per day since 20 July, while I was closing somewhere between two and five. 221 of 254 drop-box entries had been written by AI sessions rather than by me, 163 of them from sessions in the orchestration repo itself.
Nothing was broken. Every one of those orders was reasonable. That is exactly the problem. A Claude Code session wants to be useful, and if you open one in any real repository it will find ten genuine improvements: a refactor that would be cleaner, a doc that is out of date, a test that could be tighter. Multiply ten reasonable observations by fifteen repositories and you get a stream no single person will ever drain. My inbox was not filling up with mistakes. It was filling up with good ideas.
The fix inverts the burden of proof. Until then an order existed unless somebody actively deleted it. Now it only comes into existence if a trigger can be named: a fault that hits a user or customer, a risk to security, money or data, a deploy blocker, or me saying so out loud. “Would be cleaner”, “noticed in passing”, “for consistency” is not a trigger. Those go into the session summary I read, not into the pipeline. Applying it backwards archived 19 meta-orders in one go and took the total from 165 to 144.
The thing I deliberately did not build here is the interesting one. The obvious move is an AI gatekeeper: let a model judge each incoming order and reject the noise. I did not, because the same week a badly worded order had come through that was long, well structured and entirely plausible, and which, if taken literally, would have routed the outgoing mail of 186 customer mailboxes through a service that was never meant to carry it. A model gatekeeper would have waved that one straight through. It would have contained precisely the failure class it was supposed to catch. Some problems do not get better by adding another model to them.
The guard that was looking at the wrong thing
The most recent one is from yesterday, and it is the fourth time I have hit this same class of failure.
One session writes only to its own repository, enforced by a pre-tool hook, and until yesterday that hook worked by inspecting commands. It knew the shapes that write to a foreign path: output redirection, tee, git -C, sed -i. On 28 July a rollout script wrote into 21 foreign repositories through a Python heredoc. The hook saw python3 with a string attached and waved it through, because a heredoc is not on the list of things that look like writing.
So I stopped guessing at intent. A check now records the git state of every repository before an autonomous run and compares it afterwards. It does not care how a change was made, only whether one appeared where it should not have, and it fails closed: a missing check blocks the dispatcher, a failed baseline aborts the order, an absent baseline counts as a discrepancy rather than an all-clear.
Pattern matching guesses what a command will do. Comparing before and after measures what it did. It took me four attempts to learn that.
What it costs and what is still open
The orchestration layer is 32 shell scripts and about 6,300 lines, with 145 tests that run against copies and never touch the real orders. Twenty-one hooks fire around tool calls: repository boundaries, resource locks, one that blocks a database dump flag which once destroyed 199 of 200 rows, one that refuses outgoing mail containing facts that are not on a verified list. None of it was designed. Each piece sits where something went through.
Four places still stop and wait for me. An order does nothing until I approve it, including the ones I filed myself. Work marked dialogue does not run without me. Finished work never merges itself. And anything mentioning payments, customers or a production deploy is held even when approved, because on 27 July a fifty-euro customer refund was sitting in the queue, approved, marked autonomous, ready to go.
Today: 149 orders on the books, 122 approved and waiting. The trigger rule slowed the inflow, it did not reverse it, and I am not sure it can be reversed, because this is not a bug in the tooling. A machine that generates candidate work faster than a human can evaluate it always ends in a queue. Every gate I have built is a place where I chose to be the bottleneck on purpose.
If I were starting again at the end of last year with ten projects and one Markdown file each: build the drop box and the approval gate first, and nothing else. Those two are the only parts I have never had to repair. Everything else I built twice.
Get new posts by email
One mail when a new post is out. Nothing else: no drip campaign, no tracking, no "quick question" follow-ups. Unsubscribe with one click, any time.
Double opt in: you get a confirmation mail first. Your address is used only to send these posts. Controller: Martin Schenk S.L. · Privacy