Four gates between a keystroke and production.
Agents write most of the code here. That only works because nothing they write reaches production on trust - it reaches it through four checks, each catching a different class of mistake, each one able to say no.
A fast writer needs a slow gate.
An agent can produce a correct-looking change in ninety seconds. The bottleneck was never typing - it is knowing whether the change is true. So the pipeline is built the other way round from the usual advice: writing is cheap and unguarded, landing is expensive and guarded four times over.
- Each gate catches what the previous one cannot see. The editor hook sees one file. The commit hook sees the change set. CI sees the whole repository. The queue sees the repository as it will be after the merge - which is a different thing, and the one that bites.
- A gate that only ever says yes is not a gate. Every check here has been run in the failure direction on purpose, because a check tested only on healthy input is indistinguishable from a check that always passes.
- No bypass for the owner. Branch protection includes administrators. The point is not distrust; it is that a rule with an exception for the person most likely to be in a hurry is a rule with an exception for the most dangerous case.
What each gate is actually for.
Pick one. The detail is the real configuration, not a simplification for the page.
The fourth gate exists because green is perishable.
A pull request tested on its own branch tells you the change was good against the main branch as it was when you started. If someone else landed something in the meantime, that answer has quietly expired - and nothing on the page turns red to tell you.
- The queue retests, it does not re-ask. Every pull request is built onto the current main on its own branch, tested there, and fast-forwarded only if that build passes. Waiting shows as BLOCKED - which is the normal state, not a fault.
- It replaced a rule that made every merge invalidate the others. With strict up-to-date branch protection, one merge sent every other open pull request back for a full CI round. A sprint with five parallel changes spent most of its time waiting; one cascade took six rounds.
- It is self-hosted, for a boring reason. The native queue on a private repository sits behind an enterprise plan. The service is a container on our own storage node, and if it stops, merges stop - a trade we took knowingly, with a documented manual fallback.
Green on your branch is a claim about the past. The queue asks about the present.
One change, all the way through.
A real one from this week: a gate that refused to record its own refusals. Six steps from the first keystroke to production.
Three failures from one day, and what each one taught.
All three happened on 2026-08-17, while the changes described above were going through. None of them was a bug in the code being shipped - which is exactly why they are worth writing down.
A test job reported failure. The cause was a build runner that lost its network connection, not the change. Checking the runners took one command and saved an hour of looking for a defect that was not there.
A red result names a measurement, not always a system.
Two changes written hours apart both extended the same list - one so refusals stop inflating a metric, one so demotions do not. Git called it a conflict. Neither version was right on its own; the merge needed both entries.
Parallel work collides with itself long before it collides with anyone else.
Mid-afternoon GitHub reported a twenty percent error rate across web and API. We stopped merging and deploying, verified by hash that the pushed work had actually landed, and waited.
During an outage a command's exit code is a rumour. The remote state is the fact.
And then, every night, something tries the doors.
The four gates ask whether a change is safe to ship. None of them can answer the question that matters afterwards: is the thing that is already running still safe today? At 03:00 UTC a scan runs against production - the real one, not a copy - and looks at it the way an outsider would.
- Four passes, none of them destructive. The TLS configuration is graded, a scanner sweeps for known vulnerabilities and misconfigurations, a passive proxy walks the pages, and the public API surface is fuzzed for server errors. Rate-limited on purpose; the intrusive, denial-of-service and brute-force categories are switched off. This is a check, not an exercise in proving we can break our own site.
- The fuzzer builds its own target list. It reads the API schema straight from the application object, keeps only the paths the code itself marks as public, and sends nothing but GET. It fails on one thing: an undocumented server error. A 404 is an answer; a 500 is a bug with a stack trace behind it.
- What it deliberately does not touch. One internal hostname is out of scope and named in the workflow with the reason - it is not in public DNS, so scanning it would be measuring a door that the outside world cannot reach.
Of the last thirty runs, twenty-seven passed, two failed and one was cancelled. The failures are the point: a nightly scan that has never once gone red is not evidence of a secure system, only of a scan nobody wired to anything.
A gate protects the change. This protects the thing the changes added up to.
The shape of it, in counted numbers.
19 editor hooks across 6 events - formatting, secret scanning, a destructive-command guard on the shell, and a check that refuses to read stale context.
Two git hooks on deliberately disjoint paths, sharing one rule file. A clean merge never passes through the commit hook, so without the second one the check was silently optional.
13 CI jobs, folded into 2 required checks. 496 test files, run in three tiers: bulk, 25 that need their own process, and 31 behind an isolation gate.
4 scans against production at 03:00 UTC - TLS grading, known vulnerabilities, a passive crawl, and a GET-only fuzz of the public API. 27 of the last 30 runs passed.
None of this makes an agent careful. It makes carelessness expensive and visible.
This is what I mean when I say the guardrails are the product.
Any team can run agents that write code. The question is what stands between what they write and what your customers see. Here it is four things, each of them measured, each of them able to refuse.
Book a call Or just write to me
Related: the system this pipeline ships and how an agent earns autonomy.