I went to bed at about half nine, and the last thing I typed to the session before I did was this:
I am going to head off to bed now, but I would like you to continue the rollout and create all the MRs for waves 1-5, but do not merge them, we will review together in the morning.
The whole governance model is in that last clause
An estate-wide migration off a third-party errors library onto our own, across dozens of modules. It's mechanical and tedious, just the kind of job you want to hand over, and just the kind that goes badly wrong without making a sound.
I could hand it over and then sleep because everything up to the merge button is recoverable... a branch, a merge request, the lot. Forty merge requests sitting open overnight are, at absolute worst, forty things to close in the morning (and a mild feeling of having wasted an evening's compute).
The merge is where it stops being recoverable, so the merge is where the stop went. Once I'd seen that the stop was mine to place, leaving it running overnight stopped feeling like much of a gamble.
The morning, and what I did first
The work looked good, it really did.
So I typed back this, and it stopped short of an approval:
that looks really good! it does however raise a concern, only 26 MRs? we have 40+ projects, have we missed some... were some using stdlib errors instead of cockroachdb/errors... if so then that means we need to do a second pass
I hadn't read a single diff at that point, or opened a merge request. I'd looked at a count, held it up against a number I happened to know, and noticed they didn't match.
The agents didn't make a mistake
In one sense the night had gone perfectly well. The brief was to migrate every module using the third-party library, and each module using the third-party library had been migrated, correctly, one merge request each, unmerged, as asked. Audit the work against the instruction and it passes cleanly.
The hole was in the instruction. A chunk of the estate hadn't been on that library in the first place: those modules sat on plain standard-library errors, and they needed migrating just as much, for the same reasons. They weren't in scope because at half past nine at night it hadn't occurred to me (the one holding the map) that they were a separate category.
So the agents had done exactly what I asked, and what I'd asked for was short a good dozen modules. Better agents wouldn't have helped with that one (a better brief would).
The check a fleet can't run on itself
What caught it was arithmetic. Twenty-six against forty-plus! It wasn't a code review or a test, just one comparison between the shape of what came back and a number I'd been carrying around in my head, a number the fleet had no way of knowing. It only knows how many projects it was pointed at, which is a different number, and the gap between those two is invisible from the inside.
That's the most valuable review I do over unattended work, and it was a while before I saw it as a pattern rather than a lucky glance. And I'm inclined to say it's rarely reading the diffs. The diffs are the part the machine is good at (most of the time) and reviewing them line by line at that volume becomes mostly theatre, at least for something as well shaped as this migration was meant to be. It's things like: is this the right number of things? Does that total look like the estate I remember? Why is this one three times the size of the others?
Cheap, instant, and unavailable to the fleet, since each of those questions needs something that wasn't in its context.
The judgement calls it couldn't have made either
The review didn't stop at the count. The session had flagged three merge requests it had concerns about and left them for me instead of deciding for itself, which is the behaviour I want, and each of them needed a judgement call.
One had introduced a deprecation. I turned it into a hard removal instead. What was being deprecated was a hack, a very recent one, and I could walk it back easily enough if I was wrong. That's three pieces of context that live in my head rather than in the code: how old it is, what I thought of it when I wrote it, and how much it would cost to reverse.
Another changed a user-facing error message one release after that text had shipped. I accepted it as it was. The release window was short and the project was pre-release, so the usual compatibility care didn't apply.
The third had got a test passing by adding an entry to a dependency allowlist, to cover a neighbouring module that hadn't been migrated yet. By the time I read it the session had reworked that into an allowance with a tripwire on it, one that fails once the dependency has gone and the entry is no longer needed, and I was happy to leave it there.
They're all decisions I'd rather make myself, and I don't think any of the three could have been made from the diff alone.
Then the boring part that actually took the thinking
Merging forty-odd interdependent modules is not a loop. Do it in any old order and each merge triggers dependency-bump merge requests in everything downstream, which then race each other, and the CI churn is enormous and largely pointless. So the releases went leaves first, working up the dependency graph, so that by the time each module was released the things it depended on had already settled.
That part of the morning took real thought, and I suspect it's what an unattended run would have been least likely to get right. It needs the whole graph held in view at once, and it needs you to care about a mess that lands somewhere other than where you're standing.
The bookend, three nights later
A near-identical permission on a different night, in very similar words: I am going to bed now so please action this while I'm asleep, you have my permission to create a private repo.
By morning skillup existed, from nothing.
That was a much easier thing to agree to, and the reason's fairly obvious once you say it. A throwaway private repository is about as reversible as anything gets. Same pattern, different worst case, so the stop went in a different place.
Twenty-six against forty
I've thought about that count a fair bit since. It took about four seconds, it wasn't clever and it needed no expertise at all, just knowing roughly how many projects there are... which you only know from having built them one at a time.
Originally published at phpboyscout.uk on 8 November 2026.