ci: re-anchor the next -rc.N on tag cut + backward-version guard (#308) - #315
Merged
StefanSteiner merged 9 commits intoSep 8, 2026
Merged
Conversation
…uard Document the release: published re-run that re-anchors the next -rc.N on the freshly cut tag, the backward-version guard that backstops it, and the runbook reorder that promotes the release PR's autorelease: tagged label before cutting the tag so the re-run cannot abort. - GITHUB_OPERATIONS.md: workflow table row + trigger; publish-mechanism fix (maintainer publishes the Release, not release-please); rc-line self-bump now notes the anchoring precondition and re-trigger; new 'The backward-version guard' subsection; graduation section clarifies the guard catches the opposite (backward) failure; branch-protection and re-verifying cross-references. - CONTRIBUTING.md: step 4 notes the same event re-anchors the next rc. - AGENTS.md: release-please re-runs on release: published to re-anchor.
`python3 -m unittest .github/scripts/test_verify_release_pr_version.py`
fails: the leading dot in `.github` is parsed as a relative-module
reference ("Empty module name"). Point both the docstring and the
GITHUB_OPERATIONS.md reference at `python3 <path>`, which runs from the
repo root.
Final-sweep review found the comparator's 12-case regression suite was never invoked in CI — only manually or implicitly when a real release PR fires the guard, i.e. at the exact high-stakes moment the guard exists to protect. Since the version-forward job already always runs, make checkout unconditional and add an always-run step executing test_verify_release_pr_version.py, so a regression to the semver logic is caught on every PR. Also document two assumptions the review surfaced: the jq '."."' read depends on the single-'.'-package (include-component-in-tag: false) layout, and the PR-only cancel-in-progress expression is intentional forward-safety, not a dead conditional.
Align Step 1's docstring with the shipped test file: the `python3 -m unittest <path>` form fails on the dot-leading .github path.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
release-please miscomputes the next
-rc.Nafter a manual-tag release cut. Underthis repo's gated flow (
skip-github-release: true), merging the release PR doesnot tag — a maintainer hand-cuts the
vX.Y.Ztag + Release afterward. On therelease-PR merge push the new tag doesn't exist yet, so release-please falls
back to scanning full history, where a stale
Release-As: 1.0.0-rc.2footer in anancestor commit drags the next version backward to an already-published rc
(#306 proposed
1.0.0-rc.2after1.0.0-rc.3shipped). Publishing a backwardversion is unrecoverable — crates.io never frees a version number.
Closes #308.
The fix (defense in depth)
Primary — re-anchor on tag cut. Add
release: types: [published]torelease-please.yml's triggers.gh release createnow re-runs release-pleasewith the tag present, so it anchors on the just-cut tag, scans only post-tag
commits (excluding the stale footer), and regenerates the release PR with the
correct next
-rc.N.skip-github-release: truemeans this re-run creates noRelease, so there is no self-trigger loop; the constant
concurrency: release-pleasegroup serializes it against the merge-push run.Backstop — backward-version guard. New
verify-release-pr-version.yml+verify-release-pr-version.py(stdlib-only, full semver-2.0 precedence) fail arelease PR whose proposed manifest version is behind
main. The job always runs(reports
successon non-release PRs, so it is safe to require); the comparisonis gated to the
release-please--branches--*PR. Its 12-case regression suiteruns on every PR.
Runbook reorder. The manual-tag runbook now promotes the release PR's
autorelease: taggedlabel beforegh release create, so therelease: publishedre-run cannot abort on "untagged, merged release PRoutstanding."
The fix survives multiple more rc cuts and the eventual
1.0.0graduation:1.0.0sorts above every1.0.0-rc.N(a forward move), so neither the re-run northe guard obstructs graduating.
Docs
docs/GITHUB_OPERATIONS.md,CONTRIBUTING.md, andAGENTS.mdare reconciled totell one consistent story (trigger, label-before-tag ordering, what the guard
catches vs. doesn't); a new "The backward-version guard" subsection documents the
check; the implementation plan is committed under
docs/superpowers/plans/.Mark
verify-release-pr-versionas a required status check onmain(Settings → Branches). Until then the guard is advisory — a red X a human can
click past. The always-run job design makes it safe to require: it reports
successon every non-release PR and only fails a release PR that regressed theversion.
Verification
rc.4 → 1.0.0graduation / equal → exit 0, parse & usage errors → exit 2; readsthe live
1.0.0-rc.3manifest correctly.markdownlint-cli2clean.the CI-test-wiring fix (the fast reviewer's one Important finding, now addressed).
CI-and-docs-only change — no Rust / library code touched, so no
cargogateapplies.