chore: prune orphaned -rc.N tags + automate self-bootstrap rc-tag lifecycle #153
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit#153
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
The phenomenon
Operator surfaced 2026-06-27: the self-bootstrap re-pin discipline (AGENTS.md §2) tags each in-cycle re-pin commit as
vX.Y.Z-rc.1. These tags exist purely to give the consumer'srelease.ymla stable ref to pin to between cuts. Once the cut forvX.Y.Zships, the corresponding-rc.1tag is superseded — no consumer references it; it's substrate dead-weight.13 cycles of in-cycle re-pin discipline have produced 13
-rc.Ntags. Only 1 is currently live; 12 are orphaned.Inventory at 2026-06-27 01:36 UTC
Why the orphans exist
The self-bootstrap re-pin pattern (#82/#91/.../#146):
vX.Y.Z-rc.1at the merge SHA → consumer'srelease.ymlre-pins@vX.Y.Z-rc.1vX.Y.Zis created at the rolling-PR-merge commit (DIFFERENT SHA than the -rc.1)vX.Y.Z-rc.1is now superseded; no live consumer references itvX.Y.Z+1-rc.1The orphan accumulates because we never DELETE the superseded
-rc.1.The empirical v0.5.1-rc.1 artifact (worth noting)
v0.5.1-rc.1was tagged butv0.5.1was never shipped. The v0.5.1 sprint produced afeat:commit which auto-bumped to v0.5.2 (same pattern that produced v0.10.2 instead of v0.10.1, v0.9.0 instead of v0.8.1, etc.). The-rc.1tag was orphaned before its cut completed.This pattern would also produce orphans going forward whenever the operator overrides the bump via the
bump/*label (e.g.,bump/patchon afeat:commit produces v0.X.Y not v0.X+1.0; the rc.1 was tagged with the intended-but-not-shipped name).Proposed cleanups
(A) Historical: bulk-prune the 12 orphaned -rc tags now
git tag -d <orphan>+git push origin --delete <orphan>. Safe because:Substantive but small: ~12 tag deletions. The toolkit's release-counter in the Forgejo UI decreases from 14 to 2 (v0.10.4-rc.1 + cellblock's referenced refs).
(B) Going forward: automate the rc-tag lifecycle
Option 1 — Delete on cut: when the cut path completes successfully (cut tag
vX.Y.Zis created), also deletevX.Y.Z-rc.1(and any othervX.Y.Z-rc.Ntags). The cut workflow has the auth to do this via the Forgejo API.Option 2 — Delete on next re-pin: when the next
vX.Y.Z+1-rc.1is tagged, also delete the previous-rc.1. This requires the re-pin process to know which previous rc to delete; less clean than (1).Option 3 — Leave them + accept the noise: keep the substrate-of-record but acknowledge the tag-list growth. Could move the rc tags to a non-default Forgejo "release type" if Forgejo distinguishes pre-releases prominently. (It does — they show as "Pre-release" labels, but they still appear in the releases list.)
My lean: (B)/Option 1 — cleanest. The cut workflow has the auth + the timing is unambiguous (post-cut, the rc.1 IS orphaned). ~10 lines of bash in
_release.yml's cut path.(C) Substantive alternative: eliminate rc tags entirely via build-bake (#148)
If #148 (build-bake
toolkit_ref) ships, the consumer'srelease.ymlstill pinsuses: ...@<ref>. The<ref>could be:-rc.1tag (current discipline)@abc1234) — ugly but tag-cleanmainoverlay — brittle@<commit-SHA>is the substrate-cleanest option but introduces SHA-based pins which are less readable + harder to reason about for operators. Trade-off worth considering as part of #148's scope.Composition
toolkit_reffrom consumer release.yml; the script's tag-resolution logic usesgit rev-parse $PINNED_REF. If a tag is deleted while a consumer pins to it, the script fails loud ("FATAL: pinned ref '$PINNED_REF' does not resolve"). The pre-1.0 historical-prune (A) is safe ONLY if no historical pin still points at an orphan.v0.10.4-rc.1is in toolkit-self's release.yml. cellblock pins@v0.10.4-rc.1too. No historical pin references any orphan. ✓Implementation surface
For (A) historical prune
scripts/prune-orphaned-rc-tags.sh(one-shot helper that lists orphan candidates + deletes after operator confirmation) OR just a one-linerfor tag in $(git tag --list 'v*-rc*' | head -N); do git tag -d $tag && git push origin --delete $tag; donewith the explicit listFor (B) automate
_release.yml's cut path: after release-create succeeds, find + delete the${CUT_VERSION}-rc.*tags via Forgejo API. Best-effort (failure doesn't break the cut)Estimated scope: ~30 lines + 1 PR cycle.
What this PR does NOT do
-rc.1going forward; we just clean up after-rc.Ntag between cutsvX.Y.Z-rc.1is tagged then bump-override produces a different version on cut, the orphan is created by abandonment, not by supersession. (B)/Option 1 would NOT catch this case; could be a follow-upRefs
-rc.Ntags in the repo at 2026-06-27 (12 superseded + 1 abandoned + 1 live)Operator confirmation 2026-06-27: agree to (B)/Option 1 (delete on cut). When engaged: ~10 lines in
_release.yml's cut path post-release-create + bats coverage + AGENTS.md §2 amendment documenting the lifecycle. (A) historical-prune of the 12 superseded + 1 abandoned orphan can bundle with the same PR or ship separately.