ref:4fb06f82042940ee7204eaa1e42ddac84f13e46a

fix(ci): stop a released tag from triggering another release

`release` ends by pushing a tag. That push starts a CI run, and that run's HEAD is still the main commit — so the "am I on main?" SHA gate passes, and it releases again, tags again, and starts another run. It is not hypothetical. 2026.7.6, 2026.7.7 and 2026.7.8 are three releases of the identical commit a7e0745, each minted by the run the previous one's tag push started. The chain stopped only because an unrelated 403 broke it, not because anything noticed. The SHA gate cannot catch this by construction: a tag pointing at the main commit is indistinguishable from main by SHA. Only the ref distinguishes them, so both `release` and `bump-anvil-pin` now refuse a run whose ANVIL_BRANCH is under refs/tags/. Without the guard on `bump-anvil-pin` too, every tag would also open a second identical pin-bump PR. ANVIL_BRANCH carries the full ref — the runner injects `pipeline_run.branch`, which PushConsumer sets to `event.ref` verbatim. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SHA: 4fb06f82042940ee7204eaa1e42ddac84f13e46a
Author: Cole Christensen <cole.christensen@gmail.com>
Date: 2026-08-05 02:47
Parents: aa652cb
1 files changed +27 -0
Type
.anvil.yml +27 −0
@@ -102,6 +102,22 @@
git config --global --add safe.directory /workspace
export MIX_HOME=/workspace/.mix
# Never release from a tag-triggered run. This step ENDS by pushing a
# tag, and that push starts another run — one whose HEAD is still the
# main commit, so the SHA gate below passes and it releases again, tags
# again, and so on. It is a self-feeding loop, and it ran: 2026.7.6,
# 2026.7.7 and 2026.7.8 are three releases of the identical commit
# a7e0745, stopped only by an unrelated 403 breaking the chain.
#
# The SHA gate cannot catch this on its own — a tag on the main commit
# is indistinguishable from main by SHA. Only the ref tells them apart.
case "${ANVIL_BRANCH:-}" in
refs/tags/*)
echo "Skipping release: tag-triggered run ($ANVIL_BRANCH)"
exit 0
;;
esac
# Only release from main branch
# CI checks out a detached HEAD so git branch name is always "HEAD".
# Compare the checked-out SHA against origin/main instead.
@@ -183,6 +199,17 @@
set -e
git config --global --add safe.directory /workspace
export MIX_HOME=/workspace/.mix
# A tag-triggered run is not a merge to main, and its HEAD is the main
# commit, so the SHA gate below would wave it through and open a second
# identical pin-bump PR for every tag `release` pushes. Same reasoning
# as the guard in `release` — see the comment there.
case "${ANVIL_BRANCH:-}" in
refs/tags/*)
echo "Skipping pin bump: tag-triggered run ($ANVIL_BRANCH)"
exit 0
;;
esac
# Same gate as `release` above: CI checks out a detached HEAD, so the
# branch name is always "HEAD". Compare the checked-out SHA against