Skip to content

ci(release)!: release through Changesets with an alpha prerelease channel - #2430

Merged
jeffredodd merged 7 commits into
mainfrom
ci/changesets-release
Nov 7, 2026
Merged

jeffredodd merged 7 commits into
mainfrom
ci/changesets-release

Conversation

@jeffredodd

@jeffredodd jeffredodd commented Nov 7, 2026 •

Copy link
Copy Markdown
Member

What this changes

Releases now go through Changesets, ported from Fiestaboard/FiestaUI#375. Every merge to main that touched code used to cut a release. Now:

  1. Shipped PRs add a changeset. Run npx changeset, pick patch/minor/major, and write one user-facing line. CI's new Release guards job (part of CI Success) fails a PR that changes shipped files without one.
  2. Release keeps one Version PR open. On every push to main/alpha, the Release workflow runs changesets/action@v2 and maintains a single "chore: release" Version PR. That PR bumps package.json, both lockfiles, web/package.json and src/__init__.py, and writes CHANGELOG.md.
  3. Merging the Version PR releases. The workflow builds the multi-arch image from that commit and pushes the manifest. After that it creates the tag and the GitHub Release, so installs are never offered an image they can't pull. On stable it then dispatches the FiestaPi build, the docs snapshot and the Docker Hub description.

There are two channels. main releases X.Y.Z to :latest. alpha is a long-lived branch in pre mode with tag beta. It releases X.Y.Z-beta.N to :beta as a GitHub prerelease, which only beta-channel installs see. Both names were confirmed with the maintainer.

Design and lifecycle: docs/internal/development/RELEASING.md. Plan: docs/internal/plans/2026-11-07-changesets-release.md.

Key decisions

  • No publish script. Nothing goes to npm. scripts/release/plan.py releases when package.json's version has no GitHub Release yet. Re-runs after a failed build resume, and the action's "empty changesets" no-op can't strand a release.
  • Trigger is push on [main, alpha], with no path filters. Concurrency is per branch and never cancels. Version PRs are opened with the Claude bot app token, so their CI isn't held in action_required.
  • Pre-mode guards (scripts/release/pre_mode.py):
    • A PR into main can't bring pre mode; exit mode is allowed, because that's the promotion.
    • A PR into alpha must keep the beta pre tag.
    • The Release workflow skips on alpha in exit mode and fails without pre.json.
  • Changeset check (scripts/release/changeset_check.py):
    • "Shipped" is an allowlist mirroring the Dockerfile's COPY sources plus pi-image/. A test parses the Dockerfile so a new COPY can't slip past.
    • fiestaupdater/, tests, Markdown, docs and CI don't need a changeset. This keeps the feat(fiestaupdater): install a named tag, so a box can change channel #1969 lesson.
    • Waived by an empty changeset, by the no-changeset label (read live, so a re-run picks it up), or by head refs changeset-release/*, main (syncs), alpha (promotion) and dependabot/*. Dependency bumps ride along with the next release.
  • Pi image is dispatched, not chained. build-fiestapi.yml no longer listens for the release workflow's completion, because most runs only update the Version PR. It takes a release_tag input and builds from that tag.

Removed

  • release-beta.yml and its tests.
  • From release.yml:
    • the label-driven bump;
    • the chore: bump version push to main with RELEASE_PAT;
    • revert-on-failure, which force-pushed main.
  • The bump-label step in pr-label.yml. Area labels stay.
  • The version buckets in .github/release.yml.
  • The bump modes of version-sync.js. It now only syncs, and it accepts prerelease versions, which are now committed.
  • RELEASE_CHANNELS.md, replaced by RELEASING.md.
  • next in CodeQL, replaced by alpha.

Behaviour changes worth knowing:

  • The daily plugin-previews.json push from Scheduled: Maintenance no longer cuts a patch release. It rides along with the next one.
  • The release workflow is now named "Release".

Also fixes: E2E image check on Docker 29

E2E has failed on every PR that builds the test image since Nov 6, fiestaui-upgrade included, with "Loaded image does not match the producer's image ID".

  • Cause: ubuntu-latest moved to ubuntu-26.04, which brings Docker 29. Docker 29 defaults to the containerd image store, where docker image inspect .Id after docker load is the image's manifest digest. The old check compared it against buildx's imageid, which is the config digest.
  • Fix: build-test-image now also exports digest, and both E2E jobs accept either of this run's two identities and nothing else.
  • Checks: reproduced and verified in docker:29-dind (containerd store): real digests are accepted, wrong or missing ones rejected. tests/ci/test_image_handoff.py covers both stores.

Downstream

FiestaUI's upgrade PRs (fiestaui-upgrade) change web/package*.json, which is shipped. They are deliberately not exempt. The companion PR makes them carry .changeset/fiestaui-upgrade.md: Fiestaboard/FiestaUI#376. It's a no-op for repos without .changeset/, so the two PRs can merge in either order. Until it lands, an open upgrade PR needs a changeset (or the label) added by hand. claude-issue-triage.yml and CLAUDE.md tell Claude to write changesets.

Verification

  • pytest: the new tests/test_release_guards.py and tests/test_release_workflow.py, plus the 24 existing test files that read workflows, versions or package files (846 tests in all). All pass.
  • ruff check/ruff format, npm run docs:lint and actionlint report nothing new. The only actionlint findings are in older steps.
  • Simulated npm run version-packages in a scratch clone:
    • stable: 10.3.2 → 10.4.0, with all five version files and CHANGELOG.md;
    • pre: pre enter beta → 10.4.0-beta.0 → 10.4.0-beta.1, then pre exit → 10.4.0 with a merged changelog and pre.json deleted.
    • plan.py and pre_mode.py gave the expected decision at each step, against the live GitHub Releases.
  • act pull_request -j release-guards ran four scenarios: no changeset fails, with a changeset passes, Version PR head ref passes, and a PR into alpha without pre.json fails. act needed the GitHub remote set for GITHUB_REPOSITORY. Labels are now fetched with the Python standard library because the act image has no gh.
  • act -n on release.yml shows the expected job plan.
  • Not run locally: the version job end to end, which needs the app token and live repository writes, and the Docker build and release jobs. These are first exercised by the first Version PR after merge.
  • Two independent Fable reviews, one of the plan and one of the implementation; their findings are folded in. The implementation review caught a critical bug: changesets/action leaves the Version PR's bump in the working tree, which would have made the first push with a pending changeset release the next version built from old code.
    • The fix: the step resets and cleans the tree, plan.py reads package.json from git rather than from disk, and it checks that the build commit carries the version.
    • Reproduced against a deliberately dirty tree: the plan now answers "already released".
  • Head-ref exemptions apply only to same-repo branches, so a fork PR from a contributor's main is still checked.
  • Plugin README.md changes are deliberately not "shipped", matching the old paths-ignore: '**.md'.

After merge (each setting confirmed first)

  1. Create the alpha-protection ruleset: PR plus CI Success, no merge queue, merge commits allowed, deletion allowed, and bypass for RepositoryRole 5 and Integration 4139279.
  2. Create alpha from main, run npx changeset pre enter beta, and push.
  3. The frozen next branch is untouched. Archiving it is a separate decision.

🤖 Generated with Claude Code

jeffredodd and others added 6 commits November 7, 2026 13:56
…nnel

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The changesets action leaves the Version PR's bump in the working tree;
plan.py now reads package.json from git and checks the build commit carries
the version. Head-ref exemptions apply only to same-repo branches, and the
release job can read PR comments again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs review

Verdict: looks-good - the docs changes are accurate, well-voiced, and consistent with the workflow changes they describe.

Checked and clean:

  • Accuracy. The Changesets flow, the two-channel (main/alpha) model, the release-guards gate, the shipped-files allowlist, and the re-run / promotion / restart lifecycle in RELEASING.md all match release.yml, ci.yml, and the scripts/release/ guards. Referenced source files (src/system/update_service.py, scripts/version-sync.js, and the three scripts/release Python guards) exist; the package name fiestaboard matches the changeset frontmatter.
  • Links. .changeset/README.md, CONTRIBUTING.md, and RELEASE_CHECKLIST.md all point to RELEASING.md via relative paths that resolve. The deleted RELEASE_CHANNELS.md had exactly one referrer (CONTRIBUTING.md), and this PR rewrites that reference, so no dangling link remains.
  • Voice / branding / privacy. No marketing filler, no FiestaBoard vs Vestaboard confusion, no real PII. (The Fiestaboard/FiestaBoard occurrences are the literal GitHub org and repo slug, correct as-is.)

Minor, non-blocking, no action needed: the plan doc (docs/internal/plans/2026-11-07-changesets-release.md) lists RASPBERRY_PI.md among docs to update for the Pi-trigger change, but this PR does not touch it. That is fine - its wording (published by build-fiestapi.yml after each app release, roughly 45-60 min) describes the outcome, not the workflow_run-to-dispatch mechanism, so it stays accurate.

No inline suggestions - nothing warranted a one-line fix.

Auto-review by claude-docs-review.yml.

ubuntu-latest moved to ubuntu-26.04 (Docker 28.0.4 -> 29.4.2) between Nov 4
and Nov 6. Docker 29 defaults to the containerd image store, where a loaded
image's ID is its manifest digest, not the config digest buildx reports as
imageid, so every E2E job failed "Loaded image does not match the producer's
image ID". The producer now also exports its digest and consumers accept
either of this run's two identities, and nothing else.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added major and removed major labels Nov 7, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs review

Verdict: looks-good with one nit.

Scope: the docs changes only (.changeset/README.md, PULL_REQUEST_TEMPLATE.md, CLAUDE.md, CONTRIBUTING.md, docs/internal/development/RELEASING.md, the removed RELEASE_CHANNELS.md, docs/internal/plans/2026-11-07-changesets-release.md, RELEASE_CHECKLIST.md). The workflow/script/test changes were skimmed for context only.

Verified against the PR head:

  • Referenced scripts exist: scripts/release/changeset_check.py, plan.py, pre_mode.py, scripts/version-sync.js.
  • package.json has the changeset and version-packages scripts and @changesets/cli v3, matching RELEASING.md and the plan doc.
  • The CI job is named Release guards (ci.yml), matching every doc reference.
  • All relative links resolve: the .changeset README, CONTRIBUTING.md, and RELEASE_CHECKLIST.md all point to the new RELEASING.md.
  • No real PII, no FiestaBoard/Vestaboard confusion, no claims about Vestaboard hardware internals.

One inline suggestion below - click Apply suggestion to commit it.

Auto-review by claude-docs-review.yml.


## After merge (each repo-settings change confirmed with the user first)

1. Leave the frozen `next` branch alone for now (decided 2026-11-07: the prerelease branch is `alpha`). Archiving or deleting `next`, and removing the disabled "Release: Publish Beta Image" workflow entry, is a separate, confirmed step.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Grammar: use "an" before the vowel sound in "alpha".

Suggested change
1. Leave the frozen `next` branch alone for now (decided 2026-11-07: the prerelease branch is `alpha`). Archiving or deleting `next`, and removing the disabled "Release: Publish Beta Image" workflow entry, is a separate, confirmed step.
2. Create an `alpha-protection` ruleset, modeled on FiestaUI's `alpha-protection`

@jeffredodd
jeffredodd merged commit ea49965 into main Nov 7, 2026
20 checks passed
@jeffredodd
jeffredodd deleted the ci/changesets-release branch November 7, 2026 23:31
@github-actions

github-actions Bot commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

SKIP: CI/release infrastructure change — migrates to Changesets; no user-facing behavior change

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file docs major

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant