ci(release): sweep every tag for a missing release, not just this run's #24
Reference in New Issue
Block a user
No description provided.
Delete Branch "ci/release-orphan-tag-sweep"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Follow-up to #23, which merged while this was being written. Checking
linkandservuo-pluginsfor the same gaps (link#33, servuo-plugins#15) turned up a third one — and showed that #23's description of it was wrong.What #23 got wrong
It said the plan step "can adopt an orphan tag — but only on a run that reaches it, and a later push with no releasable commits sets
RELEASE=falseand stands down first."That understates it, and in a way that matters. The recovery is version-scoped. It computes
VERSIONfrom the newest tag plus the conventional-commit bump, then only ever checksrefs/tags/v${VERSION}.So it recovers an orphan on the very next run and is useless afterwards: once any releasable commit lands, the next run computes a new version and never looks at the old tag again. The orphan becomes permanent and silent — not "until someone pushes", but forever.
servuo-plugins proved it, and the proof is pointed
servuo-pluginsv0.1.0had been orphaned since 2026-08-04 — tag present, no release, no assets — while v0.1.1, v0.2.0 and v1.0.0 all published normally.The reason is worth reading twice. The commit that added the recovery to that repo was itself:
Typed
fix(...), so it bumped the version to v0.1.1 — and the run that introduced the recovery stepped straight past the tag it was written to rescue. It never looked back, and structurally could not have.Why the retry in #23 is not enough on its own
The retry makes an orphan much less likely, but not impossible: a cancelled job or a dying runner produces the same state with no 500 anywhere. Until now, nothing would ever have mentioned it again.
What this does
The plan step now sweeps every
v*tag and warns about any without a release.It warns rather than recovers, on your decision. Publishing an old version would mean building today's tree and shipping it under a tag whose tree it is not — worse than the inconsistency it fixes — and a routine push silently republishing ancient history is not something this pipeline should be able to do. Recovery stays limited to the version the run computed.
It never fails the run. A sweep that can break a good release is a sweep someone will delete.
Verification
Run against the real repositories rather than a stub, since the only thing worth proving is that it tells a clean repo from a dirty one:
and again after servuo-plugins#15 deleted that tag — all three clean. Every run block
bash -nclean, YAML parses, no empty${{ }}token.One confirmation from #23
It predicted that typing the commit
ci(...)rather thanfix(...)would cut no release. Run 79 fired on the merge, succeeded, and installer is still on v0.1.1. The bump rule behaved exactly as described.AI-assisted: written with Claude Code (Opus 5).
🤖 Generated with Claude Code
The first commit on this branch said the plan step "CAN recover an orphan, but only on a run that reaches it". Checking link and servuo-plugins for the same gaps showed that understated it. The recovery is VERSION-SCOPED. It computes VERSION from the newest tag plus the conventional-commit bump, then only checks refs/tags/v${VERSION}. So it recovers an orphan on the very next run and is useless afterwards: once any releasable commit lands, the next run computes a NEW version and never looks at the old tag again. The orphan becomes permanent and silent. servuo-plugins proved it, and the proof is pointed. Its v0.1.0 had been orphaned since 2026-08-04 -- tag present, no release, no assets -- while v0.1.1, v0.2.0 and v1.0.0 all published normally. The commit that ADDED the recovery to that repo was itself typed "fix(release): preflight credentials and recover the orphaned v0.1.0 tag", so it bumped to v0.1.1, and the run that introduced the recovery stepped straight past the tag it was written to rescue. The retry added in the previous commit makes an orphan much less likely, but it does not make one impossible -- a cancelled job or a dying runner produces the same state with no 500 anywhere -- and until now nothing would ever have mentioned it again. So the plan step now sweeps every v* tag and warns about any without a release. It WARNS rather than recovers, on the org lead's decision. Publishing an old version would mean building today's tree and shipping it under a tag whose tree it is not, which is worse than the inconsistency it fixes; and a routine push silently republishing ancient history is not a thing this pipeline should be able to do. Recovery stays limited to the version the run computed. It also never fails the run. A sweep that can break a good release is a sweep someone will delete. Verified by running the loop against the real repositories rather than a stub, since the only thing worth proving is that it tells a clean repo from a dirty one: link (9 tags): clean servuo-plugins (4 tags): :⚠️:Tags with no release: v0.1.0 installer (2 tags): clean and again after servuo-plugins#15 deleted that tag, where all three report clean. Every run block bash -n clean, the YAML parses, and no empty template token. Companion PRs: link#33 and servuo-plugins#15. Co-Authored-By: Claude <noreply@anthropic.com>