commit&push

~/blog $ git show 2026-09-03

Merge Is Not Deploy

· 3 min read · Victor Benavides

--ops--craft

On Tuesday morning, the newest post on this blog was merged and returning a 404 at the same time.

Not a bug. The pull request was approved and merged, the branch was gone, the commit was sitting on the development branch — and the post did not exist on the internet, because the thing that actually publishes is a promotion to another branch, and nobody had run it yet.

Merged. Not deployed. Two states, and I had been treating them as one.

MERGEDon the branchDEPLOYEDrunning in prodVERIFIEDchecked — and howa green workflow is not a running poda running pod is not a working featurethe one word that hides all of it: "done"
Three states, two gaps. Most of the wrong answers to “is it live?” come from collapsing all three into a single word, and the collapse is invisible until someone asks you to be certain.

The first gap

A merge puts code on a branch. That's all it does.

Between there and production sits a deployment, and a deployment is a separate event that can quietly not happen. The workflow didn't fire because the push came from an automation token. The promotion needed a human and the human was answering a different question. The build went green and shipped an artifact that was never rolled out.

Every one of those leaves you with a green checkmark and unchanged production. The checkmark is telling the truth — it just isn't answering the question you're asking it.

The second gap

The one people skip is worse, because it looks finished.

A pod can be running and serving errors. A service can deploy perfectly with a config value it needs still missing. A feature can be fully live and switched off behind a flag — which is often deliberate, and still means the answer to "can someone use it?" is no.

Deployed means the code is there. It does not mean the behaviour is there. Those come apart constantly, and they come apart silently, because nothing in your tooling is designed to notice the difference.

Why it costs something

Collapsing these into "done" doesn't hurt until someone asks you to be certain.

Then you find yourself saying you're fairly sure something is live — and building on top of that, or assuming a protection is on, or rebuilding a thing that already shipped. The failure isn't dramatic. It's a decision made on a state you never actually checked.

What fixes it is boring

Give the intermediate states names and let them be real answers.

"Built, not deployed" is a status. "On dev only" is a status. Neither of them is a failure or an excuse — they're just true, and having the words available stops you rounding everything up to "done."

Then make one rule: something is live when it has been checked against production, with a note of how it was checked. Not merged. Not deployed. Checked. A URL you loaded, a request you made, a row you saw.

The blog you're reading now publishes when a promotion runs, and I know it's live because I asked production for the page and production gave it to me. That's a lower bar than it sounds, and it's still higher than most of us clear.

$ grep -rl --tag ~/blog