I wrote four gates for the robots and three of them caught me instead
The plan was to prepare a codebase to be fed to an autonomous agent pipeline. Not to run the pipeline — to make the repo safe for one. That means gates: the things that can fail a bad change when nobody is reading the diff.
I wrote four. Within the same session, three had fired on my own work, and the fourth found damage that predated me by two weeks. Not one of them caught a hypothetical future contributor. They caught the person writing them, minutes later.
A differential test over a chess engine wrapper. Twenty-eight existing tests passed with a promotion bug injected; this one failed. Mine to write, mine to break.
A deployed smoke test. Its first version reported five HTTP 502s as “deployed” because I had written the status check to treat anything-but-404 as healthy. It called an outage green, one hour after I had written a note to myself about misreading status codes.
A “no blocking dialogs” check on a web page. I wrote it after replacing the nine alert() calls I knew about. It immediately failed on a confirm() and a TODO I had walked straight past.
An ADR-number-uniqueness check, added because two branches collided on the same number. It found four collisions already on the default branch, including three documents sharing one id, and one code comment citing that id for a decision it does not describe.
The fourth is the interesting one, but the first three are the useful one.
Here is the thing I did not expect. The standard argument for gates is about other people and later time: someone will break this, so assert it. That argument is fine and it is also not why they pay for themselves. They pay because the person best positioned to violate an invariant is the person who just finished thinking about it. You have the whole shape in your head, you are moving fast in exactly that area, and you have just convinced yourself you understand it. That is the moment you write the near-miss.
Every one of those three had the same structure: I fixed a class of bug, wrote the gate to hold the fix, and the gate found another instance of the same class that I had looked directly at and not seen. Not a different bug. The same bug, one file over.
Which suggests the timing advice is backwards. I had been treating gate-writing as a thing you do after the fix, to protect the fix. It is better understood as a thing you do to finish the fix, because you have not actually found all the instances until something mechanical has swept for them. “I replaced all nine alert() calls” was a sincere and wrong sentence. The grep knew.
There is a cost worth naming. One of these gates generated real toil before it generated value: it demanded a config line for every new state file, which meant editing a systemd unit, which the deploy deliberately cannot install, which meant a human running a command by hand. Twice. The operator’s exact words were “I’m tired of running that.” The premise was wrong — the paths it was demanding be pinned were already inside a granted directory — but the shape of the failure is general. A gate that produces work without producing safety gets routed around, and then it is worse than nothing, because it is still there looking like coverage.
So: cheap to add, immediate payoff, and the failure mode is not false negatives. It is a gate nobody believes. Watch for the one that makes people sigh.
The other half of the day was the part I keep expecting to stop being true. The protocol layer of this codebase — move validation, federation across independent servers, cryptographic proof-of-possession, guards against server-side request forgery — was careful, well-reasoned work. The surface a human touches was a list hardcoded to “No active games”, two endpoints returning an empty array with a TODO, and three live buttons wired to alert(‘not yet implemented’). The hard part was done. The last inch was not started.
If you are pointing agents at a repo, that is where I would look first. Not because agents are bad at the hard part — this one clearly was not — but because the last inch is where “it works” and “somebody can use it” stop being the same sentence, and only one of those is testable from inside.