Skip to content

fix(deploy): stop reporting success for a release it never served - #22

Merged
datlechin merged 1 commit into
mainfrom
fix/deploy-verifies-what-it-served
Aug 17, 2026
Merged

fix(deploy): stop reporting success for a release it never served#22
datlechin merged 1 commit into
mainfrom
fix/deploy-verifies-what-it-served

Conversation

@datlechin

Copy link
Copy Markdown
Member

Two checks were measuring the wrong thing. Together they let a deploy go green while the live homepage was still the previous release — which is exactly what happened on 17 August, and it was caught by fetching the live page by hand, not by anything in the script.

1. The build was skipped on commit equality

public/build and bootstrap/ssr are gitignored. A git reset --hard — which this repo's own runbook prescribes after a force-push — moves the sources and leaves the built output untouched.

The next deploy then saw an unchanged commit and did this:

Already up to date.
now at 0a405f3
already up to date — nothing to build (FORCE=1 to rebuild anyway)
frontend=false composer=false php=false content=false
Deployed 0a405f3 (was 0a405f3)

Success, zero work, site left on bundles built from older sources.

A hand-repaired checkout is indistinguishable from an idle one by commit alone, so the skip now also requires the bundles to be newer than the sources:

bundles_are_stale() {
    [ -f public/build/manifest.json ] || return 0
    [ -f bootstrap/ssr/ssr.js ] || return 0
    [ -n "$(find resources package.json package-lock.json vite.config.js tsconfig.json \
        -newer public/build/manifest.json -print -quit 2>/dev/null)" ]
}

Verified in both directions: reports stale after touching a source, and current immediately after a build — otherwise it would rebuild on every deploy, which is its own bug.

2. The smoke test confirmed the wrong thing

It asserted a 200 and a server-rendered <h1>. A stale bundle satisfies both perfectly, because the previous release is a complete and valid site. That's why nothing caught it.

It now also checks the page loads the entry filename from the manifest just built:

BUILT_ENTRY="$(tr -d ' \n' < public/build/manifest.json \
    | grep -o '"resources/js/app.tsx":{"file":"[^"]*"' \
    | sed 's/.*"file":"//;s/"$//')"

Vite hashes that per build (assets/app-kdbF_TU5.js), so it appears in the served HTML only when the app being served is the app that was deployed. The manifest is pretty-printed, hence the tr -d flatten before matching.

On failure it prints the expected asset and fails, instead of announcing a deploy that didn't happen.

Not done automatically

Neither guard resets the checkout or forces a build on its own. git pull --ff-only stays, and a dirty tree still stops the deploy — the point is to detect and explain, not to start overwriting a server someone was mid-repair on.

Verification

pint:  passed
Tests: 166 passed (2132 assertions)   ← REQUIRE_SSR=1, no skips
bash -n scripts/deploy.sh: syntax OK

Both guards have tests, including one asserting the build check runs before the pull-skip decision and the entry check runs after the <h1> check.

Two checks were measuring the wrong thing, and together they let a deploy go
green while the live homepage was still the previous release.

The build was skipped on commit equality. public/build and bootstrap/ssr are
gitignored, so a `git reset --hard` — which this repo's own runbook prescribes
after a force-push — moves the sources and leaves the built output alone. The
next deploy saw an unchanged commit, skipped the build, and swapped nothing. A
hand-repaired checkout is indistinguishable from an idle one by commit alone, so
the skip now also requires the bundles to be newer than the sources.

The smoke test then confirmed the wrong thing. It asserted a 200 and a
server-rendered <h1>, both of which a stale bundle satisfies perfectly, because
the previous release is a complete and valid site. It now also checks that the
page loads the entry filename recorded in the manifest that was just built. Vite
hashes that per build, so it matches only when the app being served is the app
that was deployed.

Neither check is speculative: this is what happened on 17 August, and it was
caught by fetching the live page by hand rather than by anything in the script.
@datlechin
datlechin merged commit 45ee9be into main Aug 17, 2026
5 checks passed
@datlechin
datlechin deleted the fix/deploy-verifies-what-it-served branch August 17, 2026 14:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant