Skip to main content
日本語

Deploy SUCCESS Is Not a Production Check

Tomohiro Iida · Published August 25, 2026 · Updated August 25, 2026

The moment a deploy workflow turns green, the report becomes “it is live”. What the green actually proves is that the deploy command ran to completion. It says nothing about which code production is serving, whether the database state matches what that code requires, or what a signed-in customer can see. Netsujo now treats Deploy SUCCESS and Production verification as two separate stages, and verifies SHA identity, schema state, public and authenticated routes, browser evidence, cleanup, and rollback readiness one at a time.

Key takeaways

  • Deploy SUCCESS means the operation completed. It is not evidence that production behaves as expected.
  • Check the current main SHA, the deploy input SHA, the deployment artifact SHA, and the Production alias SHA separately.
  • Never judge with a value that does not change for the change you made. A generic uptime check cannot detect a failure limited to authenticated routes.
  • Public routes and authenticated routes are different observations. A signed-out visitor can see a healthy site while every signed-in customer sees an error.
  • Schema state, browser evidence, synthetic identity cleanup, and rollback readiness are each their own check.

Incident card

FieldValue
IncidentAfter a completed production deployment, only signed-in customers received a 500 on every screen.
SymptomNo cookie or an empty cookie returned 200. A non-empty cookie returned 500. Public pages stayed at 200.
False assumptionThe deploy succeeded and the public pages return 200, therefore production is healthy.
Root causeCode that had not been merged to main was deployed from a local working directory, and the database column that code required did not exist in production.
Immediate fixRoll back to a deployment that had previously been served as Production, then measure that the authenticated route recovered.
System fixDisable Git-triggered deployment on every branch, restrict production release to a single manual workflow with a confirmation string, record the source commit, and separate post-deploy verification.
Remaining riskProduction can move during verification, a registered classification can hide a real defect, and rollback targets age out.

After a green deploy, only the customer screens were down

On 2026-08-16 the production customer portal returned 500 to signed-in customers. With no session cookie the response was 200. With an empty cookie value it was also 200. With a non-empty cookie the response was 500 regardless of whether the value was valid. Public pages stayed at 200, so signed-out visitors saw a healthy site.

The runtime error was SQLSTATE 42703, undefined_column. The column the code referenced did not exist in the production database. Two further facts came out of the investigation. The production deployment in question carried no commit SHA in its deployment metadata, because it had been pushed from a local working directory rather than through a Git integration. And the migration that added the column lived on a branch that had not been merged to main, so it had never been applied to the production database.

The deploy operation itself had succeeded. It was green.

The false assumption was in the choice of signal

The first assumption is that a successful deploy means production is new. A deploy result reports whether a command ran to completion. What is being served has to be confirmed separately.

The second is the choice of signal. Do not judge with a value that does not change for the change you made. A 200 on the public home page does not move when an authenticated route breaks. A generic uptime check stays green when a single route fails. A build result knows nothing about the database. A deployment timestamp does not say what was deployed.

Watching a value that cannot move produces false negatives. Watching a value that moves independently of the change produces false positives. Choose a signal that changes only because of this change.

Root cause

The immediate fix

Recovery was a rollback to a deployment that had previously been served as Production. After the rollback we measured that even an invalid cookie value returned 200 on the authenticated route. Recovery was fast because a rollback could be triggered from a local machine. That same permission was also the cause of the incident.

Turning it into a system

SubjectWhat to look atWhat it tells you
SHA identityCurrent main, deploy input, deployment artifact, Production aliasWhat is actually being served
Database stateThe live schema, read through a read-only connectionWhether it matches what the code requires
Public routeHTTP response and rendered output for key pathsThe signed-out experience
Authenticated routeThe customer screen after sign-inFailures visible only to customers
Browser evidenceScreenshots across viewports plus console and network errorsBreakage you cannot see without a browser
CleanupRemoval of the synthetic verification identityWhether production data stayed clean
Rollback readinessExistence and provenance of a rollback targetWhether recovery is possible during an incident

Decision contract: deploy result and production verification are separate variables. If the Production SHA differs from the deploy input SHA, verification fails. If schema state does not match the code requirement, verification fails. If no authenticated route was observed, verification is unknown rather than pass. If the signal used for the judgement does not change with this change, the signal is invalid.

A reusable production verification checklist

Evidence

StatementClassBasis
On 2026-08-16 only authenticated routes returned 500 while public pages returned 200.OBSERVEDRepository incident record governance/incidents/INC-2026-08-16-003.yaml
The runtime error was SQLSTATE 42703, undefined_column.OBSERVEDRepository incident record governance/incidents/INC-2026-08-16-003.yaml
The production deployment carried no commit SHA metadata.OBSERVEDRepository incident record governance/incidents/INC-2026-08-16-003.yaml
Git-triggered deployment is disabled on every branch, including main.IMPLEMENTEDRepository vercel.json
Production release is restricted to a manual workflow that requires a confirmation string.IMPLEMENTEDRepository production deploy workflow
A static check rejects deploy policy drift; an agent branch self-allowlisted and produced 11 previews in two hours on 2026-08-16.IMPLEMENTEDRepository script scripts/check-vercel-deploy-policy.mjs
The source commit record is verified both statically and immediately after the deploy.IMPLEMENTEDRepository script scripts/deploy-policy/check-deploy-provenance.mjs
Post-deploy QA resolves the production alias SHA before and after the QA.IMPLEMENTEDRepository production deploy workflow
A read-only workflow reads the live production schema directly.IMPLEMENTEDRepository workflow for production schema verification
Authenticated screens are visited with a browser and the evidence contract is counted again.IMPLEMENTEDRepository audit workflow and scripts/signal-audit/assert-signal-service-evidence.mjs
The synthetic identity is fixed in code and never taken from workflow input.IMPLEMENTEDRepository module scripts/signal-audit/production-goal-smoke-identity.ts
Rollback targets are limited to main history plus prior production delivery.IMPLEMENTEDRepository production rollback workflow
The choice of signal determines whether false negatives or false positives appear.INFERREDDerived from the observations and implementations above
Wider automatic judgement of production verification.PROPOSEDToday a human holds the final decision

Limits of application

Remaining risks

Conclusion

Deploy SUCCESS is a report that an operation finished. Production verification is an observation of what is running now. Keep SHA identity, schema state, public and authenticated routes, browser evidence, cleanup, and rollback readiness as six separate things, and the moment a green report drifts away from production reality, the drift becomes visible.

Frequently asked questions

If the deploy workflow is green, is the release complete?
The release operation is complete. Whether production behaves as expected is separate. Observe the SHA behind the production alias, the database state, and both public and authenticated routes before calling it done.
If public pages return 200, is production healthy?
That cannot be concluded. Some failures are limited to authenticated routes. In the 2026-08-16 incident, a 500 appeared only when the session cookie was non-empty, while public pages stayed at 200.
Is an uptime check enough?
No. A generic uptime check does not change with your change. Judge with signals that move only because of this change: SHA identity, the response of the specific route, and the live schema state.
If post-deploy QA fails, should the deploy be repeated?
First confirm that the release already happened. The deploy job and the QA job are separate, and rereading a red workflow as “not deployed” leads to shipping the same build again.
Is it acceptable to create a verification account in production?
Only when the cleanup is designed as well. Fix the identifier in repository code rather than accepting it from workflow input, because the deletion path holds production database authority and a caller-supplied identifier would let it operate on arbitrary accounts.

Previous: parallelise implementation, serialise integrationEpisode 09 covers running several AI agents without breaking the shared repository.

Next: routing everything through owner approval stalled developmentEpisode 11 sets authority and evidence by risk level instead of approving everything.

Back to the full incident logThe series hub lists all twelve episodes and the order to read them in.

We design deploy path restriction, source commit provenance, live schema checks, authenticated route audits, and rollback readiness as one operating system.

Talk to Netsujo about AI development operations