ci(release): sweep every tag for a missing release, not just this run's #24

Merged
whitlocktech merged 1 commits from ci/release-orphan-tag-sweep into main 2026-08-24 19:42:03 +00:00
Member

Follow-up to #23, which merged while this was being written. Checking link and servuo-plugins for 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=false and stands down first."

That understates it, and in a way that matters. The recovery is version-scoped. It computes VERSION from the newest tag plus the conventional-commit bump, then only ever 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 — not "until someone pushes", but forever.

servuo-plugins proved it, and the proof is pointed

servuo-plugins 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 reason is worth reading twice. The commit that added the recovery to that repo was itself:

fix(release): preflight credentials and recover the orphaned v0.1.0 tag

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:

link (9 tags): clean
servuo-plugins (4 tags): ::warning::Tags with no release: v0.1.0
installer (2 tags): clean

and again after servuo-plugins#15 deleted that tag — all three clean. Every run block bash -n clean, YAML parses, no empty ${{ }} token.

One confirmation from #23

It predicted that typing the commit ci(...) rather than fix(...) 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

Follow-up to **#23**, which merged while this was being written. Checking `link` and `servuo-plugins` for 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=false` and stands down first."* That understates it, and in a way that matters. **The recovery is version-scoped.** It computes `VERSION` from the newest tag plus the conventional-commit bump, then only ever 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 — not "until someone pushes", but **forever**. ## servuo-plugins proved it, and the proof is pointed `servuo-plugins` `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 reason is worth reading twice. The commit that **added** the recovery to that repo was itself: ``` fix(release): preflight credentials and recover the orphaned v0.1.0 tag ``` 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: ``` link (9 tags): clean servuo-plugins (4 tags): ::warning::Tags with no release: v0.1.0 installer (2 tags): clean ``` and again after servuo-plugins#15 deleted that tag — all three clean. Every run block `bash -n` clean, YAML parses, no empty `${{ }}` token. ## One confirmation from #23 It predicted that typing the commit `ci(...)` rather than `fix(...)` 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](https://claude.com/claude-code)
wtclaude added 1 commit 2026-08-24 18:58:35 +00:00
ci(release): sweep every tag for a missing release, not just this run's
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 1m28s
65998692ae
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>
whitlocktech approved these changes 2026-08-24 19:41:57 +00:00
whitlocktech merged commit 1996a32153 into main 2026-08-24 19:42:03 +00:00
whitlocktech deleted branch ci/release-orphan-tag-sweep 2026-08-24 19:42:04 +00:00
Sign in to join this conversation.
No description provided.