Skip to content

fix(release): pin build dispatch to the tag, fix release-branch naming - #90

Merged
EtienneLescot merged 2 commits into
mainfrom
claude/prerelease-version-tag-ee96ae
Jul 16, 2026
Merged

fix(release): pin build dispatch to the tag, fix release-branch naming#90
EtienneLescot merged 2 commits into
mainfrom
claude/prerelease-version-tag-ee96ae

Conversation

@EtienneLescot

@EtienneLescot EtienneLescot commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Two bugs in the release pipeline, both found while cutting v1.7.0-rc.1 (the first RC under the release-branch freeze, so neither path had ever run).

1. Build dispatched on the wrong ref

prerelease.yml ran gh workflow run build.yml without --ref, so the build dispatched on the default branch (main), where package.json is still the previous stable version. The RC bump lives only on the release branch, so build.yml's publish guard rejected it:

##[error]package.json version 1.6.0 does not match 1.7.0-rc.1 from tag v1.7.0-rc.1

No release was created, and the installers were stamped 1.6.0. Fixed by pinning --ref "${RC_TAG}" (and --ref "${STABLE_TAG}" in promote.yml, which only worked before because the preceding main-merge happened to leave main at the stable version).

2. Release branch naming breaks promote and defeats the freeze

  • prerelease.yml created release/v${PRERELEASE}release/v1.7.0-rc.1
  • promote.yml resolves release/v${STABLE_VERSION}release/v1.7.0

That branch doesn't exist, so promote would have failed at git fetch origin release/v1.7.0.

Worse: each re-cut created a new branch off main and force-deleted the remote branch first. Cutting rc.2 would therefore sweep in anything landed on main and destroy the cherry-picks on the rc.1 branch — silently defeating the freeze the contract exists to guarantee.

Fixed by using one branch per stable version (release/vX.Y.Z), created at rc.1 and reused for later RCs: fetch + check out when it exists, branch from the dispatched ref when it doesn't, never delete. The package.json bump moved into the same step so the version is written after the correct branch is checked out.

Verification

  • build.yml --ref v1.7.0-rc.1 (equivalent of fix 1) ran green on Windows/macOS(arm64+x64)/Linux and published v1.7.0-rc.1 with 6 assets.
  • Fix 2 is exercised by the next cut (rc.2), which will create release/v1.7.0 and let promote resolve it.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved prerelease (RC) branch and prerelease version handling so release candidates are cut and recut reliably from the same frozen release snapshot.
    • Ensured build workflows are dispatched against the intended RC and promoted stable tags, using the matching package version to avoid stale default-branch state.
    • Updated release tagging/build behavior during promotion to prevent mismatched version checks.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Release workflows now create or reuse frozen prerelease branches, tag RCs from the target release branch, and dispatch build.yml from the matching RC or stable tag.

Changes

Release workflow snapshot handling

Layer / File(s) Summary
Create and tag the frozen prerelease branch
.github/workflows/prerelease.yml
Prerelease cuts create release/v${NEXT} from the dispatched ref for rc.1, reuse it for later RCs, update package.json, and push RC tags from that branch.
Pin release workflow dispatches
.github/workflows/prerelease.yml, .github/workflows/promote.yml
Build dispatches use ${RC_TAG} for prereleases and ${STABLE_TAG} for promotions; comments document the tag-specific checkout requirement.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is informative but misses most required template sections, including Summary, Related issue, Type of change, and release impact. Add the missing template sections and label the current Verification section as Testing, with a related issue entry and change/release classifications.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title matches the main changes: pinning build dispatches to release tags and fixing release-branch naming.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/prerelease-version-tag-ee96ae

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

EtienneLescot and others added 2 commits July 16, 2026 17:11
prerelease.yml triggered build.yml without --ref, so the build ran on the
default branch (main), where package.json is still the previous stable
version. The RC version bump lives only on the release branch / RC tag, so
build.yml's publish-release guard rejected it ("package.json version 1.6.0
does not match 1.7.0-rc.1 from tag v1.7.0-rc.1") and no release was created.

Pass --ref so the build checks out the tag's tree (bumped package.json +
frozen code). Apply the same pin in promote.yml for the stable build, which
previously only worked because the main-merge happened to leave main at the
stable version first.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ross RCs

prerelease.yml created release/v${PRERELEASE} (e.g. release/v1.7.0-rc.1) but
promote.yml resolves release/v${STABLE_VERSION} (release/v1.7.0), so promote
would die at `git fetch origin release/v1.7.0`. v1.7.0-rc.1 is the first RC cut
under the release-branch freeze, so this path had never run.

Worse, each re-cut created a *new* branch off main and force-deleted the remote
branch first, so cutting rc.2 would both sweep in whatever had landed on main
and destroy the cherry-picks on the rc.1 branch. That silently defeats the
freeze the contract is supposed to guarantee.

Use one branch per stable version (release/vX.Y.Z), created at rc.1 and reused
for later RCs: fetch and check it out when it exists, branch from the dispatched
ref when it doesn't, and never delete it. Fold the package.json bump into the
same step so the version is written after the correct branch is checked out.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@EtienneLescot
EtienneLescot force-pushed the claude/prerelease-version-tag-ee96ae branch from 02759fc to 1d6a8a1 Compare July 16, 2026 15:13
@EtienneLescot EtienneLescot changed the title fix(release): pin build.yml dispatch to the release tag ref fix(release): pin build dispatch to the tag, fix release-branch naming Jul 16, 2026
@EtienneLescot
EtienneLescot merged commit d5966ed into main Jul 16, 2026
12 checks passed
@EtienneLescot
EtienneLescot deleted the claude/prerelease-version-tag-ee96ae branch July 16, 2026 15:25
EtienneLescot added a commit that referenced this pull request Aug 11, 2026
The manual fallback was the only real defect. It bumped `package.json`
with a `sed` while `set-release-version.mjs` — which prerelease.yml and
promote.yml both call — also writes `package-lock.json`. A fallback
release cut by hand therefore shipped a lockfile disagreeing with the
package it locks, the exact drift that script was written to stop. It
now calls the script, and hoists the RC version into `$RC` so rc.2+
does not need three edits kept in sync.

Also: "promote.yml is the only writer" now says *automated* writer, so
it stops contradicting the manual fallback three sections below; the
cherry-pick rule points at `git log release/vX.Y.Z..main` for seeing
what is not in the RC; the product constraint distinguishes gating on
readiness from gating on the user, so an unfinished capture backend
can still hide behind a flag; and `.harness/memory/MEMORY.md` no
longer describes the pre-#90 `release/vX.Y.Z-rc.N` naming.
EtienneLescot added a commit that referenced this pull request Aug 11, 2026
`.harness/docs/git-workflow.md` and
`technical-documentation/engineering/release-and-secrets.md` both documented
the release flow, and only one of them was ever checked. `check-docs.mjs`
walks `technical-documentation/` alone, so when #90 changed the release-branch
naming the docs tree was corrected and the harness copy was not — it kept
sending readers to `release/vX.Y.Z-rc.N`, a branch the workflows never create,
for a month. `.harness/memory/MEMORY.md` repeated it.

Both files already declared non-overlapping scopes: git-workflow.md is
"conventions for the Mavis reins", release-and-secrets.md is "the operational
reference for cutting releases", and the latter already delegated branching
and PR procedure back. The release flow sitting in git-workflow.md violated
that split. So: move, then collapse.

Moved into release-and-secrets.md, none of it duplicated there before — why
the build dispatch is pinned to the tag (GITHUB_TOKEN does not fire `push:`,
and the build must check out the tag or fail the version guard), the numbered
branch contract including "never delete or recreate", the v1.6.0 postmortem
with what actually shipped, the runnable manual fallback, backports, and
milestone handling. git-workflow.md §Release flow is now four lines and a
pointer: 154 lines to 48, in a file loaded into every agent run.

The fallback there also still said to prepare "the correct `package.json`
commit" — the same lockfile trap the harness copy had, in the file operators
actually read. It now points at set-release-version.mjs and says why.

Root cause last: `check-docs.mjs` walks `.harness/` too, and the retired
branch naming is a LEGACY identifier, so CI fails on any doc reintroducing
it. `.harness/` passes all three existing passes today, so this adds a gate
without a migration. The new test asserts the script *rejects* a planted
violation, because a checker that silently stops checking still prints OK —
which is how this rotted unnoticed in the first place.
EtienneLescot added a commit that referenced this pull request Aug 11, 2026
`.harness/docs/git-workflow.md` and
`technical-documentation/engineering/release-and-secrets.md` both documented
the release flow, and only one of them was ever checked. `check-docs.mjs`
walks `technical-documentation/` alone, so when #90 changed the release-branch
naming the docs tree was corrected and the harness copy was not — it kept
sending readers to `release/vX.Y.Z-rc.N`, a branch the workflows never create,
for a month. `.harness/memory/MEMORY.md` repeated it.

Both files already declared non-overlapping scopes: git-workflow.md is
"conventions for the Mavis reins", release-and-secrets.md is "the operational
reference for cutting releases", and the latter already delegated branching
and PR procedure back. The release flow sitting in git-workflow.md violated
that split. So: move, then collapse.

Moved into release-and-secrets.md, none of it duplicated there before — why
the build dispatch is explicit and pinned to the tag, the numbered branch
contract including "never delete or recreate", the v1.6.0 postmortem with
what actually shipped, the runnable manual fallback, backports, and milestone
handling. git-workflow.md §Release flow is now four lines and a pointer: 154
lines to 48, in a file loaded into every agent run.

The dispatch rationale is documented per workflow rather than in general,
because the two do not push their tags the same way: promote.yml uses
GITHUB_TOKEN, whose tag push does not fire build.yml's `push:` trigger, while
prerelease.yml uses the PAT because a GITHUB_TOKEN tag push is answered with a
500 from a tag ruleset — the failure that took down the v1.8.0-rc.1 cut. Only
the `--ref` pinning is required in both cases, for the publish version guard.

The fallback there also still said to prepare "the correct `package.json`
commit" — the same lockfile trap the harness copy had, in the file operators
actually read. It now points at set-release-version.mjs and says why.

Root cause last: `check-docs.mjs` walks `.harness/` too, and the retired
branch naming is a LEGACY identifier, so CI fails on any doc reintroducing
it. `.harness/` passes all three existing passes today, so this adds a gate
without a migration. The new test asserts the script *rejects* a planted
violation, because a checker that silently stops checking still prints OK —
which is how this rotted unnoticed in the first place.
EtienneLescot added a commit that referenced this pull request Aug 12, 2026
The manual fallback was the only real defect. It bumped `package.json`
with a `sed` while `set-release-version.mjs` — which prerelease.yml and
promote.yml both call — also writes `package-lock.json`. A fallback
release cut by hand therefore shipped a lockfile disagreeing with the
package it locks, the exact drift that script was written to stop. It
now calls the script, and hoists the RC version into `$RC` so rc.2+
does not need three edits kept in sync.

Also: "promote.yml is the only writer" now says *automated* writer, so
it stops contradicting the manual fallback three sections below; the
cherry-pick rule points at `git log release/vX.Y.Z..main` for seeing
what is not in the RC; the product constraint distinguishes gating on
readiness from gating on the user, so an unfinished capture backend
can still hide behind a flag; and `.harness/memory/MEMORY.md` no
longer describes the pre-#90 `release/vX.Y.Z-rc.N` naming.
EtienneLescot added a commit that referenced this pull request Aug 12, 2026
`.harness/docs/git-workflow.md` and
`technical-documentation/engineering/release-and-secrets.md` both documented
the release flow, and only one of them was ever checked. `check-docs.mjs`
walks `technical-documentation/` alone, so when #90 changed the release-branch
naming the docs tree was corrected and the harness copy was not — it kept
sending readers to `release/vX.Y.Z-rc.N`, a branch the workflows never create,
for a month. `.harness/memory/MEMORY.md` repeated it.

Both files already declared non-overlapping scopes: git-workflow.md is
"conventions for the Mavis reins", release-and-secrets.md is "the operational
reference for cutting releases", and the latter already delegated branching
and PR procedure back. The release flow sitting in git-workflow.md violated
that split. So: move, then collapse.

Moved into release-and-secrets.md, none of it duplicated there before — why
the build dispatch is explicit and pinned to the tag, the numbered branch
contract including "never delete or recreate", the v1.6.0 postmortem with
what actually shipped, the runnable manual fallback, backports, and milestone
handling. git-workflow.md §Release flow is now four lines and a pointer: 154
lines to 48, in a file loaded into every agent run.

The dispatch rationale is documented per workflow rather than in general,
because the two do not push their tags the same way: promote.yml uses
GITHUB_TOKEN, whose tag push does not fire build.yml's `push:` trigger, while
prerelease.yml uses the PAT because a GITHUB_TOKEN tag push is answered with a
500 from a tag ruleset — the failure that took down the v1.8.0-rc.1 cut. Only
the `--ref` pinning is required in both cases, for the publish version guard.

The fallback there also still said to prepare "the correct `package.json`
commit" — the same lockfile trap the harness copy had, in the file operators
actually read. It now points at set-release-version.mjs and says why.

Root cause last: `check-docs.mjs` walks `.harness/` too, and the retired
branch naming is a LEGACY identifier, so CI fails on any doc reintroducing
it. `.harness/` passes all three existing passes today, so this adds a gate
without a migration. The new test asserts the script *rejects* a planted
violation, because a checker that silently stops checking still prints OK —
which is how this rotted unnoticed in the first place.
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