Skip to content

fix: deployment hygiene — concurrency, CNAME, per-branch previews#3

Merged
danielnaab merged 10 commits intomainfrom
fix/deployment-hygiene
Apr 28, 2026
Merged

fix: deployment hygiene — concurrency, CNAME, per-branch previews#3
danielnaab merged 10 commits intomainfrom
fix/deployment-hygiene

Conversation

@danielnaab
Copy link
Copy Markdown
Member

Summary

Fixes four deployment issues surfaced right after the first merge to main.

  • Concurrency: key on the acted-on branch, not github.ref. A delete of feat/foo and a push to main no longer share a group, so branch cleanup can't cancel a production deploy.
  • CNAME: copied into dist/ by the build driver on production builds (basePath === '/'). Previews never carry a CNAME. The workflow's dead fallback is removed.
  • Per-branch preview environments: every branch deploys to preview/<sanitized-branch>, surfaced in the repo Deployments UI and on PR pages.
  • Cleanup on branch delete: mark every deployment in preview/<branch> as inactive and DELETE the environment via the REST API. Unexpected API failures now surface as ::warning:: annotations rather than silent || true.
  • Recovery: workflow_dispatch now publishes (not just builds), so manual reruns work without pushing a dummy commit.

Also: updated docs/deployment.md to match the new workflow; design spec and implementation plan committed under notes/.

Reviewer callouts

  • First real test of new cleanup code will be when this branch itself is deleted after merge. The path has only been validated by code review.
  • One-time follow-up after merge: delete the legacy shared preview environment (gh api --method DELETE /repos/flexion/flexion.github.io/environments/preview). This workflow won't touch it.
  • Known unmitigated edge case: simultaneous main push and a branch delete could race on gh-pages. cleanup-preview has no retry. Low frequency; follow-up if we see flakes.

Test plan

  • bun test tests/build/cname.test.ts — 2/2 pass locally (production build copies CNAME, preview does not).
  • CI test subset (tests/catalog tests/standards tests/views tests/enhancements tests/build) — 67/67 pass locally.
  • This PR's build creates preview/fix-deployment-hygiene environment; URL https://labs.flexion.us/preview/fix-deployment-hygiene/ returns 200.
  • PR page shows a "View deployment" button pointing at the preview.
  • After merge, labs.flexion.us/ returns 200 and gh api repos/flexion/flexion.github.io/pages reports "cname": "labs.flexion.us".
  • After branch delete: preview URL 404s, deployments for the env are inactive, and preview/fix-deployment-hygiene disappears from the Deployments sidebar.
  • Subsequent push to main deploys successfully (no concurrency collision with the cleanup run).

Covers four issues surfaced after the first merge to main:
concurrency-group collision that cancels main deploys on branch
delete, missing CNAME on gh-pages, shared preview environment, and
deployments that never transition to inactive on branch delete.
Seven tasks, TDD-first where a test surface exists. Covers the CNAME
fix in the build driver and the four workflow changes in deploy.yml,
then an end-to-end verification task that exercises the full lifecycle
against the real GitHub API.
The publish job does not check out the source repo, so its CNAME
fallback silently fails and labs.flexion.us returns 404 after every
main deploy. Include CNAME in the build output instead — the artifact
then carries everything it needs to be deployed.
delete events report github.ref as refs/heads/main, so a branch-delete
run was sharing a concurrency group with the main-deploy run it
triggered — cancelling it. Keying on github.event.ref for delete and
github.ref_name otherwise scopes cancellation correctly per branch.
Previously a manual run would build and test successfully but skip
the publish job, which means workflow_dispatch could not be used for
recovery. Allow it to take the same publish path as a push.
Previously every branch landed in a shared 'preview' environment, so
the GitHub Deployments UI could not be used to navigate per-branch
state. Deploy previews to 'preview/<sanitized-branch>' so every branch
gets its own entry and its own 'View deployment' button on PRs.
When a branch is deleted, mark each of its preview deployments inactive
so the GitHub UI stops showing them as live, and delete the per-branch
environment so it drops out of the Deployments sidebar. Both calls use
the existing GH_TOKEN and are idempotent.
Bring docs/deployment.md in line with the updated workflow: per-branch
preview environments, branch-keyed concurrency, inactive-then-delete
cleanup, and the fact that CNAME is now part of the build output.
CNAME is now copied into dist/ by the build driver on production builds
(see Task 1 of this PR), so the workflow no longer needs to restore it.
The fallback was dead anyway — it referenced $../CNAME which does not
exist because the publish job doesn't check out the source repo.
The workflow was being rejected by GitHub's YAML validator with no jobs
running ('This run likely failed because of a workflow file issue'):
administration is not a valid GITHUB_TOKEN permissions scope.

Deleting an environment requires repo-admin rights that the built-in
GITHUB_TOKEN cannot hold regardless of the permissions: block. Doing
this right would require a PAT-backed secret — out of scope for this
PR.

Keep the deactivation step, which does land via deployments: write and
is the more important hygiene improvement (it stops the UI from
claiming stale previews are live). Environment entries will remain in
the sidebar.
@danielnaab danielnaab temporarily deployed to preview/fix-deployment-hygiene April 28, 2026 21:09 — with GitHub Actions Inactive
@danielnaab danielnaab merged commit f85cb00 into main Apr 28, 2026
3 checks passed
@danielnaab danielnaab deleted the fix/deployment-hygiene branch April 28, 2026 21:17
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