Skip to content

Fix: trampoline release deploys through workflow_dispatch + smoke check#96

Merged
JamboChen merged 1 commit into
JamboChen:masterfrom
lsequeiraa:fix/pages-dedup-build-version
Jun 11, 2026
Merged

Fix: trampoline release deploys through workflow_dispatch + smoke check#96
JamboChen merged 1 commit into
JamboChen:masterfrom
lsequeiraa:fix/pages-dedup-build-version

Conversation

@lsequeiraa

@lsequeiraa lsequeiraa commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

What

Fixes the silent-stale-prod failure mode of PR #89's dual-trigger workflow by trampolining release events through workflow_dispatch instead of deploying directly on the release event. Adds a post-deploy smoke check as a safety net for any future variant of the same class of bug.

Why (the v0.8.0 main page)

After v0.8.0 was released on 2026-06-05, prod stayed on v0.7.0. As of writing, https://JamboChen.github.io/endfield-calc/ still serves the v0.7.0 bundle index-CV_olKzG.js with last-modified: Fri, 05 Jun 2026 04:09:29 GMT. That timestamp is the bump-push run's deploy time, not the release run's 04:11:01 deploy time. Beta updated correctly.

Both runs reported success and showed pages_build_version: 74b7856c6bc1f02d9fc240e35cb524fde3981ac4 in their deploy step logs.

Root cause: actions/deploy-pages#383

The Pages backend silently drops the second deployment of a given SHA on release events specifically. actions/deploy-pages prints Reported success!, the deployments API marks the new deployment active and the prior one inactive, but the origin keeps serving the prior tarball. push and workflow_dispatch deployments at the same SHA are not affected. Per the issue thread and independent repro at typst-community/dev-builds#1:

Sequence at the same SHA Content swaps?
push then workflow_dispatch
workflow_dispatch then workflow_dispatch
push then release ❌ release deploy eaten
push then release then workflow_dispatch ✅ the dispatch unsticks it

Independently confirmed on this fork: two consecutive workflow_dispatch deploys of 74b7856 (with a v0.8.0 tag pushed between them) swapped content correctly; the matching release event with 74b7856 did not.

PR #89's dual-trigger design collides on every release: the version-bump push and the matching release publish always share a SHA. So every release silently keeps prod on the previous tag.

The fix: release trampoline

GITHUB_TOKEN is explicitly permitted to trigger workflow_dispatch (documented exception to the recursive-workflow prevention rule). So release events don't deploy directly. They re-trigger this same workflow as a workflow_dispatch on the default branch:

redispatch:
  if: github.event_name == 'release'
  runs-on: ubuntu-latest
  permissions:
    actions: write
  steps:
    - name: Re-trigger deploy as workflow_dispatch
      env:
        GH_TOKEN: ${{ github.token }}
        DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
      run: gh workflow run deploy.yml --repo "$GITHUB_REPOSITORY" --ref "$DEFAULT_BRANCH"

deploy:
  if: github.event_name != 'release'
  # ... existing job unchanged

Loop-safety: the dispatched run's github.event_name is workflow_dispatch, so the trampoline's if: skips and only deploy runs. Verified on the fork (see "Validation" below).

Why not other approaches

I tried what the #383 thread discusses and a few alternatives:

  • Step-level env: GITHUB_SHA: <unique> and echo GITHUB_SHA=... >> $GITHUB_ENV: the runner re-injects the real commit SHA at every step start. Submitted build_version unchanged in both attempts. Per GitHub Actions docs: "Default environment variables can't be overridden in $GITHUB_ENV."
  • Direct POST /repos/{owner}/{repo}/pages/deployments via curl and gh api with explicit Accept, X-GitHub-Api-Version, Bearer/token auth, OIDC token in body, and environment: github-pages: HTTP 404 in every configuration even with pages: write scope and a GET /pages probe returning 200. Same outcome reported by the #383 author. The endpoint refuses plain GITHUB_TOKEN.
  • Empty-commit recovery as the daily workflow: works but requires a manual step per release.

The trampoline is the only mechanism I found that's fully automatic, requires no extra permissions on the deploy job, and works without empty commits.

Smoke check (safety net)

A post-deploy step compares the entry-chunk filename (hash-suffixed by Vite, changes whenever source changes) in the just-built dist/index.html and dist/beta/index.html against the live URLs, with backoff to absorb CDN propagation. Walltime: ~0 to 2s on the happy path, capped at ~6 min on hard failure.

The trampoline is the fix; the smoke check is here so any future variant of stale-deploy weirdness (CDN regression, an upload-pages-artifact bug, a new SHA-keyed dedup mode, etc.) becomes a loud CI failure instead of silent prod drift.

End-to-end validation on fork

Reproduced the bug then proved the fix on lsequeiraa/endfield-calc, at fork master SHA 19bfeed628f897948e363dfdd21a2478a10a30b7:

  1. Baseline push deploy (run 27349007808): redispatch skipped, deploy succeeded in 55s. Submitted pages_build_version: 19bfeed.... Live = v0.7.0 bundle index-CV_olKzG.js, last-modified: 13:08:44.
  2. Tag v0.8.0 at the same SHA, publish release.
  3. Release run (27349142880, 10s): redispatch ran, deploy skipped. The trampoline called gh workflow run on the default branch.
  4. Auto-dispatched run (27349150683, 39s): deploy ran (built prod = v0.8.0), redispatch skipped (no recursion). Submitted pages_build_version: 19bfeed..., identical to step 1. Live = v0.8.0 bundle index-lcwJ2ad_.js, last-modified: 13:10:53, robots has Disallow: /endfield-calc/beta/. Smoke check passed.

Identical SHA, different content, both swapped. Exactly the case where deploy-pages silently fails today.

Tomorrow's v0.8.1 release walkthrough

  1. Merge "Bump version to 0.8.1" PR. Push run deploys (fresh SHA): prod = v0.8.0 (current latest tag), beta = master. Smoke check passes. (This step also unsticks v0.7.0 to v0.8.0 today.)
  2. Publish v0.8.1 release. Release run (~10s) trampolines through workflow_dispatch.
  3. Auto-dispatched run: prod = v0.8.1, beta = master. Smoke check passes. Live prod = v0.8.1, about 90 s after the release.

Zero empty commits, zero manual steps, zero red runs.

Files

.github/workflows/deploy.yml: adds the redispatch job, guards the deploy job with if: github.event_name != 'release', adds the Smoke-test live deployment step. Inline comments cite #383 and explain the mechanism, the alternatives that don't work, and the smoke-check role.

Refs

@lsequeiraa lsequeiraa marked this pull request as draft June 11, 2026 12:55
@lsequeiraa lsequeiraa force-pushed the fix/pages-dedup-build-version branch from e43a3c5 to 19bfeed Compare June 11, 2026 13:07
@lsequeiraa lsequeiraa changed the title Fix: catch silent stale Pages deploys via post-deploy smoke check Fix: trampoline release deploys through workflow_dispatch + smoke check Jun 11, 2026
@lsequeiraa lsequeiraa marked this pull request as ready for review June 11, 2026 14:13
The Pages backend silently drops the second deployment of a given SHA
*on release events specifically*. push and workflow_dispatch deploys at
the same SHA are NOT affected (confirmed independently in
actions/deploy-pages#383, by typst-community/dev-builds#1, and in our
own fork repro: A1 + A3 master-dispatches at SHA 74b7856 both swapped
content correctly, while the release-event run at the same SHA was
silently dropped).

The dual-trigger workflow PR JamboChen#89 added collides on every release: the
bump push and the matching release publish always share a SHA. After
the bump push deploys (prod = previous tag), the release deploy of the
same SHA is dropped server-side: actions/deploy-pages prints 'Reported
success!', the deployments API marks the new deployment active and the
prior one inactive, but the origin keeps serving the prior tarball.
This was the root cause of v0.8.0's main page never advancing from
v0.7.0 (live last-modified = 2026-06-05 04:09:29 = the bump push's
deploy time, never the release run's 04:11:01).

What this commit changes:

* New `redispatch` job that fires only on release events. It uses
  GITHUB_TOKEN's documented exception to recursive-workflow prevention
  to re-trigger this same workflow as a workflow_dispatch on the
  default branch. The dispatched run sees the new tag (it exists at
  release publish time), builds prod from it, and deploys via the
  Pages-backend code path that DOES swap content at duplicate SHA.

* The `deploy` job gains `if: github.event_name != 'release'` so
  release events skip it entirely and only the trampoline runs. The
  dispatched run's event_name is workflow_dispatch, the trampoline's
  if-guard skips, only the deploy job runs -- no recursion.

* A post-deploy smoke check that compares the entry-chunk filename in
  the just-built dist/index.html and dist/beta/index.html against
  what the live URLs serve, with backoff for CDN propagation. Demoted
  to safety-net role: the trampoline is the actual fix. The smoke
  check is here so any future variant of stale-deploy weirdness (CDN
  regression, upload-artifact bug, a new SHA-keyed dedup mode, etc)
  becomes a loud CI failure instead of silent prod drift.

Alternatives investigated and ruled out:

* Step-level `env: GITHUB_SHA: <unique>` and `echo GITHUB_SHA=... >>
  $GITHUB_ENV` -- the runner re-injects the real commit SHA at every
  step start; submitted build_version unchanged in both attempts. Per
  GitHub Actions docs: 'Default environment variables can't be
  overridden in $GITHUB_ENV.'

* Direct POST /repos/{owner}/{repo}/pages/deployments via curl and via
  gh CLI with explicit Accept + X-GitHub-Api-Version + Bearer/token
  auth and an explicit OIDC token in the body -- HTTP 404 in every
  configuration even with pages: write scope and a GET /pages probe
  returning 200. Confirmed by the #383 thread author and others; the
  endpoint refuses plain GITHUB_TOKEN. Effectively only the
  app-context auth that actions/deploy-pages holds internally works.

Recovery procedure if the smoke check ever does fire (NOT the daily
workflow -- the trampoline handles releases automatically):
`git commit --allow-empty -m 'Redeploy' && git push`.

Refs: actions/deploy-pages#383, community#170184,
typst-community/dev-builds#1.
@lsequeiraa lsequeiraa force-pushed the fix/pages-dedup-build-version branch from 19bfeed to 2b74721 Compare June 11, 2026 14:27
@JamboChen JamboChen merged commit cb272fc into JamboChen:master Jun 11, 2026
2 checks passed
@lsequeiraa lsequeiraa deleted the fix/pages-dedup-build-version branch June 11, 2026 15:38
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.

2 participants