feat(deploy): roll back to the previous release when the new one won't serve - #246
Conversation
…t serve The health check added in #243 turned a silent outage into a loud one, but it still leaves the site down: by the time it runs, pm2 reload has already swapped the process. On 2026-08-17 that would have meant a red workflow and a 503 until someone read it. So keep the previous bundle instead of deleting it. The build moves apps/web/.next aside rather than removing it, and if the port never answers, the deploy puts the old release back: reset to the previous SHA, reinstall against that lockfile, regenerate the client, restore the bundle, reload. The job still exits non-zero, and the error says plainly which commit is live. What this deliberately does not undo is the migration. `prisma migrate deploy` has already run and migrations are forward-only. That is fine for additive changes, since older code ignores columns it does not know about, but a migration that drops or renames something still needs a human. The comment in the workflow says so. pm2 save moved behind the health check as well. Persisting the process list before knowing whether the release works only makes a bad state stickier. Verified by running the script against stubbed git/pnpm/pm2/curl in a sandbox, since the failure path is the one that must not be discovered in production: a healthy deploy keeps the new bundle and exits 0; an unhealthy one with a recoverable predecessor restores the old bundle, reports that the pushed commit is not live and exits 1; and when the rollback cannot come up either, it says that instead of claiming success.
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 1 advisory finding(s) below merit a look before merge.
Graphify review — findings
Adds automatic rollback to the deploy.yml SSH script: it captures the pre-deploy SHA, preserves the old .next bundle as .next.prev, and on a failed health check restores the previous commit and bundle, reinstalls, and reloads PM2. Refactors the port health check into a reusable serving() function and defers pm2 save until after the new release is confirmed serving. Note the script explicitly does not roll back Prisma migrations.
Worth a look
- Build failure leaves previous bundle moved out of place —
.github/workflows/deploy.yml:54· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 4 functions depend on the 4 functions this change touches.
Health — grade A; no new coupling hotspots.
Verification — 4 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 4 function(s) in the blast radius were not formally verified this run
Closes the last item from the 2026-08-17 outage.
Why the health check alone is not enough
#243 made a dead release visible instead of silent, which is most of the value. It does not make it short: by the time the check runs,
pm2 reloadhas already swapped the process, so a red workflow and a 503 arrive together and stay together until a human reads the log.What changes
The build no longer deletes the previous bundle, it moves it aside:
If the port never answers, the deploy restores the previous release: reset to the SHA recorded before the pull, reinstall against that lockfile, regenerate the Prisma client, move the bundle back, reload. The job still exits non-zero, and the annotation says which commit is actually live so nobody assumes the push landed.
On success the fallback is removed again and
pm2 saveruns. It moved behind the check on purpose, persisting the process list before knowing whether the release works only makes a bad state stickier.What it deliberately does not do
Undo the migration.
prisma migrate deployhas already run and migrations are forward-only. That is tolerable for additive changes, since older code ignores columns it does not know about, but a migration that drops or renames something still needs a person. The workflow says so at the point where it matters.Verification
The failure path is precisely the one that must not be discovered in production, so it was run against stubbed
git,pnpm,pm2andcurlin a sandbox:.next.prevremoved,pm2 save, exit 0bash -non the extracted script and a YAML parse of the workflow both pass.