From 6ce58407a61f9746ed77f21200a7f19c45e5a39f Mon Sep 17 00:00:00 2001 From: bcode Date: Wed, 6 May 2026 16:33:58 -0700 Subject: [PATCH 1/2] ci(release): fail tag-push if tag is not reachable from main MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Releases must be cut from main. Tagging a feature branch (e.g. when a stacked PR has merged into a parent feature branch but not yet into main) silently produces a release whose contents diverge from the canonical history. Precedent: v0.0.8 / v0.0.9 were tagged from feat/embed-lmnr-key after PR #33 merged into the feature branch (not into main). Main moved on without it and the bcode-laminar package had to be re-landed in PR #39. This guard fails the release workflow early — before any binaries are built or uploaded — when the tag's commit is not an ancestor of origin/main. --- .github/workflows/release.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 26781ede1..95b2d5b50 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -60,6 +60,22 @@ jobs: echo "version=${VERSION}" >> "$GITHUB_OUTPUT" echo "Building tag=${TAG} version=${VERSION}" + - name: Verify tag is reachable from main + # Releases must be cut from `main`. Tagging a feature branch (e.g. when + # a stacked PR has merged into a parent feature branch but not yet into + # `main`) silently produces a release whose contents diverge from the + # canonical history. Precedent: v0.0.8/v0.0.9 were tagged from + # feat/embed-lmnr-key after PR #33 merged into the feature branch + # (not into main); main moved on without it and the bcode-laminar + # package had to be re-landed in PR #39. This guard fails the release + # before any binaries are uploaded. + run: | + git fetch origin main --depth=1 + if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/main; then + echo "::error::Tag ${GITHUB_REF#refs/tags/} points at $GITHUB_SHA which is not reachable from origin/main. Release tags must be cut from main." + exit 1 + fi + - name: Setup Bun uses: ./.github/actions/setup-bun From d81b75810e21850fedddf0c2aba7cf9bccb13359 Mon Sep 17 00:00:00 2001 From: bcode Date: Wed, 6 May 2026 16:43:50 -0700 Subject: [PATCH 2/2] ci(release): resolve tag SHA explicitly for workflow_dispatch correctness MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cubic review on PR #40: \GITHUB_SHA\ is the SHA of the ref that triggered the workflow, not necessarily the selected tag's commit. For push: tags they're equivalent, but for workflow_dispatch with inputs.tag, GITHUB_SHA is the dispatch ref's HEAD (typically main) — letting any feature-branch tag pass the ancestry check trivially. Fix: resolve refs/tags/\^{commit} via git rev-parse and ancestry- check that. Fails loudly with an actionable message if the tag doesn't exist yet (the workflow_dispatch path's failure mode for a fresh tag should be 'create the tag deliberately first', not 'silently tag at checkout HEAD'). --- .github/workflows/release.yml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 95b2d5b50..e8747f1a0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -69,10 +69,22 @@ jobs: # (not into main); main moved on without it and the bcode-laminar # package had to be re-landed in PR #39. This guard fails the release # before any binaries are uploaded. + # + # Resolves the tag name to a commit SHA via `git rev-parse` rather than + # using `$GITHUB_SHA`. For `push: tags` the two are equivalent, but for + # `workflow_dispatch` with `inputs.tag` `$GITHUB_SHA` is the dispatch + # ref's HEAD (typically main), not the selected tag's commit — using it + # would let a feature-branch tag pass the check trivially. + env: + TAG: ${{ steps.ver.outputs.tag }} run: | git fetch origin main --depth=1 - if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/main; then - echo "::error::Tag ${GITHUB_REF#refs/tags/} points at $GITHUB_SHA which is not reachable from origin/main. Release tags must be cut from main." + TAG_SHA=$(git rev-parse -q --verify "refs/tags/${TAG}^{commit}") || { + echo "::error::Tag ${TAG} does not exist locally. Create the tag on a main commit first (e.g. \`gh release create ${TAG} --target main\`), then re-run." + exit 1 + } + if ! git merge-base --is-ancestor "$TAG_SHA" origin/main; then + echo "::error::Tag ${TAG} points at $TAG_SHA which is not reachable from origin/main. Release tags must be cut from main." exit 1 fi