Skip to content

ci(release): fail tag-push if tag is not reachable from main#40

Merged
Alezander9 merged 2 commits intomainfrom
ci/tag-must-be-on-main
May 6, 2026
Merged

ci(release): fail tag-push if tag is not reachable from main#40
Alezander9 merged 2 commits intomainfrom
ci/tag-must-be-on-main

Conversation

@Alezander9
Copy link
Copy Markdown
Member

Why

Releases must be cut from main. The current release.yml triggers on any v* tag push regardless of which commit the tag points at. That means a feature-branch tag (especially when a stacked PR has merged into a parent feature branch but not yet into main) silently ships a release whose contents diverge from the canonical history.

Precedent. v0.0.8 and 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 (harness work, upstream syncs, PR #35 curl-upgrade), and the packages/bcode-laminar/ directory simply was not on main until I re-landed it in PR #39. The fact that a release shipped from a non-main commit was invisible until it caused friction with the eval team.

What

One step in release.yml, inserted right after actions/checkout and before setup-bun:

- name: Verify tag is reachable from main
  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

actions/checkout@v5 already runs with fetch-depth: 0 so the full history is local; the explicit git fetch origin main is just to ensure the remote-tracking ref is current.

Behaviour

  • Tag pushed from a main commit -> step succeeds (~1s), release proceeds as before.
  • Tag pushed from a feature-branch tip -> step fails with a ::error:: annotation, no gh release create, no binaries uploaded.
  • workflow_dispatch path (manual tag input) is also covered: $GITHUB_SHA is the resolved tag SHA either way.

Companion changes

  • Memory rule for the agent that maintains this repo: tag releases only from main; stacked PRs land bottom-up before any tag. Already pushed to agent-bcode/AGENTS.md.
  • Out of band: the admin is adding a tag-creation ruleset in repo settings that restricts who can push v* tags. Together with this CI guard, that means tags only land from main, only from approved actors.

Verification

YAML is well-formed (one run: block, no new uses:). The git merge-base --is-ancestor exit code is 0 when ancestor / 1 otherwise: both standard, both quiet on success.

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.
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 1 file

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".github/workflows/release.yml">

<violation number="1" location=".github/workflows/release.yml:74">
P1: The ancestry check uses `GITHUB_SHA`, which is incorrect for manual `workflow_dispatch` runs with `inputs.tag`; it can validate the dispatch ref commit instead of the selected tag commit.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

Comment thread .github/workflows/release.yml Outdated
…ness

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').
@Alezander9
Copy link
Copy Markdown
Member Author

Valid, fixed in d81b758.

Root cause: GITHUB_SHA is the SHA of the ref that triggered the workflow, not the selected tag's commit. For push: tags: ['v*'] they happen to be equivalent (the tag points at the triggering commit). For workflow_dispatch with inputs.tag, GITHUB_SHA is the dispatch ref's HEAD (typically main) — so any feature-branch tag would pass the ancestry check trivially as long as main was on main. The guard was a no-op in that path.

Fix: resolve refs/tags/${TAG}^{commit} via git rev-parse and ancestry-check that SHA. The Resolve tag + version step already runs first and exposes the tag name as steps.ver.outputs.tag, so it's a one-line wire-up. Also fails loudly if the tag doesn't exist locally (the workflow_dispatch failure mode for a fresh-tag dispatch should be "create the tag deliberately first", not "silently tag at checkout HEAD").

@Alezander9 Alezander9 merged commit 92f3fd2 into main May 6, 2026
3 checks passed
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