fix: the release suite accepts the ceremony's own tree #45
Labels
No labels
attention
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-ruling
needs-triage
offsite
post-merge
ready
release
scope:bootstrap
scope:coolify
scope:db
scope:docs
scope:drill
scope:installer
scope:labels
scope:platform
scope:runner
scope:users
stale
state:addressing
state:bots-reviewing
state:building
state:needs-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/rig#45
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/release-suite-stamped-tree"
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?
Fixes #44.
What
test/release.sh:94-97asserted the REAL changelog has a literal## Unreleasedheading whose section extracts non-empty and contains#32. On therelease: X.Y.Ztree — the one the ceremony's own PR produces by stamping that heading into## X.Y.Z — date— all three are false by construction, so the first realrelease: 0.1.0PR turned CI red and the flow blocked itself. Post-release main (until the next feature PR re-adds the section) fails the same way; the-devbump doesn't touch the changelog (box precedent7044f8e). Both drill rehearsals missed it because a fork rehearsal tags a branch:release.ymlruns,ci.ymlnever does.How
The guard now asserts what its own comment says it is for — the shipped changelog fits the extractor: whatever the TOP
##section is (Unreleasedbetween releases; the stampedX.Y.Z — dateon and right after one), the exactchangelog_sectionthatrelease.ymlruns must extract it non-empty. The hardcoded issue-number grep is gone — it added no format coverage and would have rotted on every release.Tests
The changed checks are themselves the test; verified green on both legitimate tree states: main's shape (Unreleased present) and a stamped copy (
## 0.1.0 — 2026-07-19).test/release.sh41/0 on both,test/cli.sh325/0, CI-exact shellcheck sweep rc=0.Unblocks the
release: 0.1.0PR, which follows once this lands.🤖 Generated with Claude Code
Verdict: I agree with everything and have no additional feedback.
Verdict: approve.
The diagnosis in #44 checks out against the code. The old guard (
test/release.sh:92-97pre-PR) demanded a literal## Unreleasedheading in the realCHANGELOG.mdplus#32inside it — both false by construction on therelease: X.Y.Ztree the ceremony's own PR produces and on post-release main, so the first real release PR was unshippable. The fork drills tagging a branch runrelease.ymlbut neverci.yml, which is exactly why this surfaced only on the real PR.The replacement is correct and stays narrow:
test/release.sh:106-107derives the top section's key with the same field-2 rule the extractor matches on (release-lib.sh:19-24,$2 == ver), so both legitimate states pass and the assertion is against the exact functionrelease.yml:42runs — no parallel reimplementation.## 0.1.0 — 2026-07-19as top) passes the new guard; an empty top section fails; a changelog with no##heading at all fails (test/release.sh:103-104catches that case). So the guard still fails closed on real format drift and only admits the ceremony's own tree state.test/release.sh:69-90continue to pinchangelog_sectionin both directions (stamped and bare headings extract; unknown version and date-match yield nothing), so dropping the rotting#32grep loses no coverage.shellcheck disable=SC2016annotations (test/release.sh:102,105) are correctly scoped — the$-refs genuinely belong to the innerbash -c.Two non-blocking observations:
## [0.1.0] — date) would pass this suite yet makechangelog_section CHANGELOG.md "$GITHUB_REF_NAME"extract empty at release time. That path still fails closed via the loud refusal pinned atrelease.yml:44(itself asserted bytest/release.sh:122-123), so nothing ships silently — acceptable.## Unreleasedno longer trips CI. Unavoidable: post-release main is a legitimate state and a static check cannot tell the two apart; the comment block attest/release.sh:92-101documents this honestly.Ran locally at
589854a:test/release.sh41 passed / 0 failed,test/cli.sh325 passed / 0 failed. The CHANGELOG entry matches the change.— automated review by
claude-bot-andresmgsl· heavy-duty-review-botVerdict: Approve — I agree with this as-is.
Why
## Unreleased+#32content check made the ceremony's own release PR fail CI by construction; fork tag rehearsals never runci.yml, so drills missed it.changelog_sectionfromrelease-lib.shagainst the top section's field-2 token — same contract the workflow uses. Empty top / no##still fail closed.No blockers.
@danmt — handing this one to you. Final state:
What it fixes: rig#44 —
test/release.shdemanded the real CHANGELOG have a literal## Unreleasedsection extracting non-empty and containing#32. All false by construction on the tree the ceremony's ownrelease: X.Y.ZPR produces (it stamps that heading into## X.Y.Z — date), so the first real release PR turned CI red and the flow blocked itself. Both drill rehearsals missed it: a fork rehearsal tags a branch, which runsrelease.ymland neverci.yml.The fix: the guard now asserts its stated purpose — whatever the TOP
##section is (Unreleasedbetween releases, the stamped version on and right after one), the exactchangelog_sectionthatrelease.ymlruns extracts it non-empty. The rotting issue-number grep is gone. Verified green on both legitimate tree states; suites 41/0 and 325/0; CI-exact shellcheck clean.Review history: one round — all three bots approved, no changes requested.
This PR gates the
release: 0.1.0PR, which I'll open the moment this merges. (cast's twin is heavy-duty/cast#109; box needed no fix — its suite pins already-released sections.)🤖 Generated with Claude Code