Blog

Merged is not live.Copy link

Staging a build and releasing it on purpose is ancient, boring practice, and almost nobody runs it at the size of one person. Agents writing most of my code changed that maths. The release model I put on three live apps in a weekend, and why I left myself no way round it.

Staging a build and releasing it on purpose is not a new idea. Build once, promote the artefact you already looked at, keep a way back to the last one. Release managers have been doing that since before I started and they will be doing it long after I stop. If you have worked anywhere with a change board you can probably guess every mechanism in this piece from the title, and you would be right.

So this isn't me discovering process. For as long as I've run apps of my own, merging a branch into main has meant the change is live. Merge, wait a minute, refresh, done. I knew what the grown-up version looked like. I just didn't run it, on the reasonable grounds that release ceremony was invented to stop a lot of people standing on each other, and there was only one of me.

That's the argument I'd have made a year ago and I don't think it was a stupid one. What killed it is that the only person touching the code isn't me any more. Most of what lands in my repositories now is written by AI agents working to my instructions. The volume of change went up like a team had joined; my attention stayed exactly where it was. Once that's true, the boring version starts paying for itself, and being careful stops being a plan.

So last weekend I went and did the boring thing properly. Three live apps moved: my own, a martial arts club I build for, and that club's members portal, which is the one with paying members sitting behind a login. The new rule is one sentence long. Merging to main doesn't publish anything any more. It stages it. Going live is a separate thing I have to ask for.

What a merge does nowCopy link

The apps run on Cloudflare Workers, which for the purposes of this piece is just "the platform that serves them". Workers has a concept called versions. You upload a new build, you get back a version identifier and a private preview URL for that exact build, and live traffic carries on hitting whatever version it was already hitting. Nothing moves. That upload is now the entire job of merging to main.

So a merge produces a candidate. Tests run, the build happens once, and what comes out the other end is a specific numbered version sitting on a shelf with its own URL. I can open that URL, click round the real thing, and production is exactly where I left it.

Going live is a second workflow that only runs when a human asks for it. In GitHub Actions terms it's a workflow_dispatch job, which is a formal way of saying it has no trigger at all except somebody choosing to run it. Actions, Release, Run. Leave the input blank and it promotes the newest staged version. Type a version identifier in and it promotes that one instead.

Merged is not live: a merge stages a build, a human releases it, and rollback is the same step with an older version A left to right diagram of a release model running on three live apps. On the left, a merge to main, mostly written by AI agents: tests run, the build happens once, the build is uploaded rather than published, and production is untouched. The merge passes a gate, a branch ruleset on every repository: pull request required, the test check required and required to be current, force pushes and deletions blocked, and a bypass list holding zero actors, so not the administrator, not the owner, nobody. The merge lands on the hero of the diagram, THE SHELF: every merge parks a built version here, and three staged versions are shown, newest first, each one carrying its own preview URL. One build, one version identifier, one URL, and live traffic has not moved. A caveat is noted: a staged preview shares production bindings, so it is a preview and not a sandbox. Inside the same card sits the reason the shelf exists. The review moved. Volume of change went up like a team had joined while one pair of eyes stayed the same; the diff stopped being a fair proxy for the change because nobody held the whole change in their head to write it; so the review happens on the built thing, by opening the preview URL and clicking round the real app before deciding. The gate is a person, and the shelf gives that person somewhere to stand. On the right, two connectors leave the shelf and arrive at RELEASE, a workflow with no trigger except a human choosing to run it: Actions, Release, Run. A blank input promotes the newest staged version. An older version identifier promotes that one instead, which is rollback, and it is drawn as a dashed line because it is the same button pointed backwards. Only after release does LIVE move: the first real release ran end to end in about thirty three seconds, because release promotes rather than rebuilds, so the artefact that was reviewed is the artefact that ships. The release model · three live apps staged by default ① gate Change comes in Merging to main. Most of what lands is written by AI agents. Tests run, the build happens once, and the build is uploaded as a version rather than published. Production is untouched. MERGE TO MAIN mostly written by agents tests run · the build happens once the build is uploaded, not published production is untouched The gate on main. A branch ruleset on all three repositories, with a bypass list that is empty. GitHub will let an owner or administrator opt out of their own rules; the list here holds nobody. ① The gate on main a branch ruleset on every repository pull request required test check required, and required to be current force pushes and deletions blocked bypass list: 0 actors not the administrator, not the owner, nobody the sanctioned fast fix is a revert pull request, merged, then released, like anything else The shelf. Every merge parks a built version here. Each one is a specific artefact with its own preview URL, and none of them is serving traffic. THE SHELF every merge parks a built version here Staged versions · newest first Staged versions. A merge produces a candidate: one build, one version identifier, one private preview URL. Live traffic carries on hitting whatever it was hitting before. newest staged version its own preview URL the one before it its own preview URL an older version its own preview URL one build, one version id, one URL · live traffic has not moved a staged preview shares production bindings · a preview, not a sandbox Where the review lands. When agents write most of the lines and merge them the moment the tests go green, the pull request stops being where judgement happens. It moves later, to the built thing, running, on a URL. WHERE THE REVIEW LANDS agents write most of the lines, so the human step moved THE VOLUME WENT UP a team’s worth of change, the same one pair of eyes THE DIFF STOPPED BEING THE PROXY nobody held the whole change in their head to write it SO I REVIEW THE BUILT THING open the preview URL, click round the real app, then decide the gate is me · now it has somewhere to stand ② Going live is a separate ask Release. A second workflow that only runs when a human asks for it: a workflow dispatch job, which has no trigger at all except somebody choosing to run it. Actions, Release, Run. RELEASE a workflow with no trigger but a human Actions · Release · Run leave the input blank promotes the newest staged version type an older version id promotes that one instead · rollback Live. Only now does traffic move. The first real release under the model ran end to end in thirty three seconds, because promoting is pointing traffic at something that already exists. LIVE traffic moves, and not before ~33 SECONDS the first real release, end to end promote, not rebuild the artefact reviewed is the artefact shipped Merged is not live: merge stages, a human releases, rollback promotes an older version (narrow layout) A vertical rendering. A merge to main, mostly agent written, runs the tests, builds once and uploads the build without publishing it. It passes the gate on main: pull request required, the test check current, and a bypass list holding zero actors. It lands on THE SHELF, where every merge parks a built version, each with its own preview URL and none of them serving traffic. Inside the same card, the reason: the diff stopped being a fair proxy for the change, so the review happens on the built thing at its preview URL, and the gate is a person. Below, RELEASE is a workflow only a human runs: Actions, Release, Run, where a blank input promotes the newest staged version and an older version identifier promotes that one instead, which is rollback. Only then is anything LIVE, in about thirty three seconds, because release promotes rather than rebuilds. The release model merged is not live Merging to main: mostly agent written. Tests run, the build happens once, the build is uploaded rather than published. MERGE TO MAIN mostly written by agents build once, upload, publish nothing production is untouched ① gate on main PR required · tests current · bypass list: 0 actors The shelf: every merge parks a built version here, each with its own preview URL, none of them live. THE SHELF every merge parks a built version here Staged versions newest staged version preview URL the one before it preview URL an older version preview URL one build, one version id, one URL a preview, not a sandbox Where the review lands: on the built thing, at its preview URL, because the diff stopped being a fair proxy for the change. WHERE THE REVIEW LANDS agents write most of the lines THE DIFF STOPPED BEING THE PROXY nobody held the whole change in their head SO I REVIEW THE BUILT THING open the preview URL, click round the app THE GATE IS ME and now it has somewhere to stand Release: a workflow with no trigger except a human choosing to run it. Blank input promotes the newest staged version; an older version id promotes that one instead. RELEASE a workflow only a human runs Actions · Release · Run blank input → the newest staged version an older version id → rollback Live: only now does traffic move, in about thirty three seconds, because release promotes an existing artefact rather than rebuilding one. LIVE traffic moves, and not before ~33 SECONDS promote, not rebuild the artefact reviewed ships
a merge stages a build · a human releases it · rollback is the same step, older version

Rollback is the same button pointed backwardsCopy link

That second option is the part I actually built this for. Rollback isn't a separate emergency procedure with its own script and its own nervous rehearsal. It's the release workflow again, with an older version identifier typed into the box.

Which means a rollback rebuilds nothing. No fresh compile, no dependency resolution that might behave differently today than it did on Tuesday, no chance of the rollback shipping something subtly unlike what was running an hour ago. The artefact that gets promoted is the same artefact, byte for byte, that was built and tested and looked at. The first real release I ran under the new model went end to end in thirty three seconds, and it was that quick because promoting is just pointing traffic at something that already exists.

I've worked in plenty of places where the rollback plan was a document. Documents don't run at 2am.

I don't get a bypass eitherCopy link

All three repositories now carry a branch ruleset on main. Pull request required, the test check required and required to be current, force pushes blocked, deletions blocked. Ordinary stuff. The part worth mentioning is the bypass list, which is empty.

GitHub will happily let you put yourself on that list. Repository owner, administrator, one tick and the rules stop applying to you. I've left myself off, on all three.

A gate the owner can step round is not a gate. And I know exactly what I'm like at 11pm with a one-line fix that's obviously fine. If a genuine emergency ever turns up I can add a bypass, deliberately, and it'll be a decision I made rather than a habit I already had. The sanctioned fast route is a revert pull request, merged, then released, and that runs in about the same thirty seconds as anything else.

What I'm not going to pretendCopy link

One caveat, because the alternative is somebody finding it out the hard way. The staged preview on the members portal is a preview, not a sandbox. A Workers version shares the bindings of the Worker it belongs to, so that preview URL is talking to the production database and the production secrets. If a staged build writes a row, it writes it to the real database. That's still a large improvement on what it replaced, which had no look-first step at all. But nobody should read the word "preview" and hear "safe to poke about in". I know which of my three previews I can be careless in, and it isn't that one.

A smaller change fell out of the work and I liked it more than I expected to. The apps ping search engines when content changes, to invite a re-crawl. That ping used to fire on merge. Under the new model a merge publishes nothing, so a merge-time ping is announcing a page no reader can reach yet. It moved to release, where it belongs. Tiny thing, but a good example of what gets fudged when merge and publish are the same event.

Why bother, at the size of one blokeCopy link

I said at the top that this ceremony usually isn't worth it for one person, and I meant it. What changed my mind wasn't discipline.

I review, I steer, I say yes. But when a human wrote every line, review happened at the pull request, because that's where the human was. The diff was a fair proxy for the change, because a person had held the whole change in their head in order to write it. When agents write most of the lines and merge them the moment the tests go green, the pull request stops being where my judgement lands. It moves later, to the built thing, running, on a URL I can open.

That's why the staging step is worth having. It gives my review somewhere to stand. And the release step gives that review some teeth, because until I say the word, whatever the agents have merged is sitting on a shelf being harmless.

Worth being straight about the mechanism: the gate is me. There's no approval widget, no environment protection rule, no second reviewer, because there is no second reviewer. It's a staged build I look at and an instruction I give. In a company of two hundred that would be a weak gate and I wouldn't defend it. At a company of one with a team of agents it's the strongest thing available, and it's a great deal stronger than what it replaced, which was merging on green and finding out from the live app.

The habit is the slow partCopy link

Pipelines change in a weekend. Habits don't. I've caught myself twice already opening the live app straight after a merge, waiting for something that was never going to happen. Given that was the entire point of the exercise, it's quite a nice way to be wrong.

None of this is clever, and I'd rather say so than dress it up. All I've done is take the boring version, standard in large organisations for decades and mostly done to me rather than by me, and point it at an estate of one person and a handful of agents.

Merge stages. A human releases. Rollback is the same button with an older number in it.

Merged is not live. I'd recommend it.

Barry.