v5.0
live available
practices ranked 05 Sept 2026

Six ways a deploy goes green while the old code keeps serving

Every one of these reports success somewhere and failure nowhere. The pattern is the same: the signal you are watching is upstream of the thing you actually care about.

By Purvansh Parmar Checked 2026-09-05 Read 7 min Entries 6

The deploy is green. The site returns 200. Neither of those facts says the code you just wrote is running.

That gap is where an afternoon goes. Each item below produced a healthy-looking signal while something specific and diagnosable was wrong underneath, and in every case the check that would have caught it was one layer lower than the check I was actually running. They are ordered by how convincing the lie is.

How this was checkedI shipped six times in one afternoon and four of those deploys lied to me in a different way each time. Two builds failed after the functions had already bundled, so the platform kept the previous deploy serving and every new route answered 200 with the old application shell. One deploy reported ready while every function threw at import. One returned valid, correct-looking JSON with an empty array in it because the code caught its own error and degraded politely.

What I compared on

01 The failed build that keeps the site up

A build fails, so the platform keeps the last good deploy serving traffic.

Wins
This is correct behaviour and you want it. A broken build should never take a working site down, and rolling forward only on success is the whole point of atomic deploys.
Loses
It means your site being up proves nothing about your last commit. I pushed, saw every route return 200, and spent twenty minutes wondering why my code behaved like code from three days earlier. It was code from three days earlier.
200 OK3 days stale

02 The catch-all rewrite

A single-page app sends every unmatched path to index.html with a 200.

Wins
Client-side routing needs it. Without that rule a deep link to a route the server has never heard of returns 404 and the app never gets a chance to render.
Loses
It converts every missing route into a success. A function that failed to deploy, a typo in a path, a route you renamed: all of them answer 200 with your homepage. The status code is structurally incapable of telling you anything.
200 instead of 404

03 The setting that exists in two places

A value committed to the config file, and the same value set in a web dashboard.

Wins
Being able to change a setting without a commit is genuinely useful during an incident, and secrets have to live somewhere other than the repository.
Loses
The dashboard wins, quietly. I set a runtime version in the committed config, watched the deploy succeed, and got the old runtime, because an environment variable set months earlier outranked the file. Nothing in the build output says which one applied.
2 sources of truth0 warnings

04 The failure before your code runs

A dependency constructs something at import time and throws before the handler is entered.

Wins
Failing at import is honest. A library that cannot work in this environment says so immediately rather than at the worst possible moment under load.
Loses
None of your error handling exists yet. The try/catch you carefully wrapped the query in is inside a module that never finished loading, so the caller gets a raw stack trace and your logging never runs. The deploy is still marked ready.
0 lines of your code executed

05 The error you handled too well

A read fails, gets caught, and returns an empty result so the page still renders.

Wins
Right for the visitor. A database blip should render an empty section, not a stack trace, and the page around it should survive.
Loses
A missing credential, a rejected credential and a genuinely empty table become the same response. I wrote this myself and then could not tell which of the three I was looking at, because I had carefully removed the difference. Degrading gracefully and reporting nothing are not the same requirement.
3 causes1 indistinguishable response

06 The gate that runs after the work

A scan or policy check that runs at the end, after compilation and bundling have already succeeded.

Wins
Scanning the built output catches things source-only scanning misses, including values injected during the build. That is worth the ordering.
Loses
The log reads as success right up until it does not, and the failure is attributed to the build script rather than the scanner. Mine reported a non-zero exit from the build command; the build command had exited zero two seconds earlier. The real message was thirty lines further down.
30 lines below the error

Side by side

PatternReportsActuallyCatch it with
Failed build keeps servingsite upold codecompare deployed commit to HEAD
Catch-all rewrite200route missingassert on the body
Dashboard overrides repodeploy okwrong valuelog the resolved value at boot
Import-time failuredeploy readyhandler never runshit the endpoint after deploy
Swallowed errorvalid responseread refusedreport why it was empty
Late pipeline gatebuild passedpublish blockedread the whole log

Verdict

The error you handled too well is the one worth changing today, because it is the only one you wrote yourself and the only one that will still be lying in six months. Every other item here is someone else's system behaving reasonably. Make the empty case say which empty it is: no credential, refused credential, or nothing to show. Then check the deployed commit rather than the deploy status, and assert on a string in teh body rather than a status code. Those three cost about ten minutes together and would have saved me most of an afternoon.

Sources