Skip to content

fix(release): route alphas through a pull request like every other release - #556

Merged
mocha06 merged 2 commits into
devfrom
rc-dev/fix/alpha-via-pull-request
Jul 31, 2026
Merged

fix(release): route alphas through a pull request like every other release#556
mocha06 merged 2 commits into
devfrom
rc-dev/fix/alpha-via-pull-request

Conversation

@mocha06

@mocha06 mocha06 commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Motivation

The alpha subcommand added in #554 could never work in this repository, and cutting the first real alpha is what proved it. A ruleset covering main and dev requires a pull request on both, so alpha's "bump, commit onto dev, push the branch, then tag" was rejected with GH013: Changes must be made through a pull request. The premise in its docstring — that dev is already the integration branch so the commit and tag can land on it directly — was just wrong, and no test caught it because none of them touch real branch protection.

Nothing was published. release.py pushes the branch before tagging precisely so a rejected push cannot leave a dangling tag, and that held: no tag locally or on the remote, no GitHub Release, PyPI 404.

The same discovery invalidates a second path. publish from main has only ever worked because its git push origin main is a no-op — the bump always arrives via a merged release PR, so no ref update is attempted and the rule never fires. The prepare on mainpublish flow that RELEASE.md documented would have failed identically.

Outcome

Alphas now reach dev through a pull request, like every other release. alpha-pr replaces alpha and is release_pr based on dev: the base is the only real difference between the two lines, and everything else derives from it — the target's track must be the one that ships from that base, only a main-bound release stamps the CHANGELOG, and the branch is named rc-<base>/release/v<target>. The alpha flow is the same reviewed path a beta takes rather than a parallel one, which is why this is a deletion of special-casing rather than an addition.

publish derives its branch from the version's track too, so one command serves both lines with no flag to get wrong, and _tag_and_publish no longer pushes a release branch at all. It asserts the branch matches its remote instead, so an unmerged release PR is reported as such rather than surfacing as a rejected push. That also fixes the main-side path above.

Two smaller things found while fixing it:

  • target_for let bump_version's ValueError escape as an unhandled traceback. release-pr beta on a line that is already a beta is an ordinary mistake, not a crash, and now reports like every other rejection.
  • Tests now cover the flow contracts unit tests had missed: that alpha-pr targets dev and does not stamp, that release-pr does stamp, and that the publish path pushes the tag and nothing else. The last one is the regression test for this bug.

Notes

The workflow-side guards from #554 — the ancestor check, the ## [Unreleased] fallback for alpha bodies, the prerelease flag, and the installer-skip verification — are still unexercised, since no alpha tag has been pushed yet. Cutting v0.5.0-alpha.1 through this corrected flow is what will validate them.

…lease

The one-step `alpha` subcommand could never work in this repository. A ruleset
covering `main` and `dev` requires a pull request on both, so committing the bump
onto `dev` and pushing the branch was rejected with GH013 — the premise that
"dev is already the integration branch, so the commit and tag land on it
directly" was simply wrong. Nothing was published: `release.py` pushes the
branch before tagging precisely so a rejected push cannot leave a dangling tag.

`alpha` is replaced by `alpha-pr`, which is `release_pr` based on `dev`. The base
is the only real difference between the two lines, and everything else derives
from it: the target's track must be the one that ships from that base, only a
`main`-bound release stamps the CHANGELOG, and the branch is named
`rc-<base>/release/v<target>`. So the alpha flow is now the same reviewed path a
beta takes, not a parallel one.

`publish` derives its branch from the version's track as well, so one command
serves both lines with no flag to get wrong, and `_tag_and_publish` no longer
pushes a release branch at all. That push was never valid: the bump always
arrives via a merged release PR, so it was either a no-op or a rule violation.
It asserts the branch matches its remote instead, turning "the release PR has
not merged yet" into a clear message. That also fixes the documented "from main
directly" path, which the same ruleset had made impossible.

Two smaller things found while fixing this:

- `target_for` let `bump_version`'s `ValueError` escape as an unhandled
  traceback. `release-pr beta` on a line that is already a beta is an ordinary
  mistake; it now reports as a release error like every other rejection.
- Tests cover the flow contracts that unit tests had missed: that `alpha-pr`
  targets `dev` and does not stamp, that `release-pr` does stamp, and that the
  publish path pushes the tag and nothing else.
@mocha06
mocha06 requested a review from adriannoes July 31, 2026 19:45

@adriannoes adriannoes left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code and tests for routing alphas through alpha-pr look solid. What still blocks merge is leftover operator docs that teach the removed one-step alpha subcommand.

What worked well

  • alpha-pr opens a PR into dev the same way product releases do.
  • Product stamp stays on main; alphas skip CHANGELOG stamp as designed.
  • _tag_and_publish pushes the tag only (no protected branch), and publish picks the branch from the version track.
  • Good regression coverage for the GH013 / branch-push removal.

Required before merge

  • RELEASE.md still presents alpha as a live subcommand (inventory around the "subcommands are …" sentence) and still says release.py alpha refuses ahead-of-main cuts, while argparse only registers alpha-pr. The new Cutting an alpha recipe is correct; these two leftovers teach a command that exits with invalid choice. Please rename both to alpha-pr (and describe open-PR-into-dev, not a one-step cut on dev). Done when no prose recommends bare release.py alpha, and uv run python scripts/release.py alpha --help is not what any sentence points operators at.

  • Under ## [Unreleased], ### Added still teaches one-step scripts/release.py alpha <bump> (bump, commit, tag, push on dev), while the new ### Fixed entry says that command is replaced by alpha-pr. Because alphas do not stamp CHANGELOG, the Release workflow uses Unreleased as the alpha body, so both bullets ship together. Please rewrite the Added release-process bullet to the current alpha-pr → merge → publish (tag only) contract, or fold the obsolete one-step description into Fixed so Unreleased teaches a single path. Done when Unreleased no longer names a deleted alpha subcommand as current procedure.

Also noted

  • _apply_prepare still lists deleted caller alpha in its docstring, and _tag_and_publish still says it pushes the branch while the body and test_tag_and_publish_never_pushes_the_branch assert tag-only push. Worth aligning those one-liners with alpha_pr and tag-only push; not merge-blocking on its own.

Review path

Trusted author, checkout at the frozen head, 49 release pytest + ruff clean, CI lint/test green. No product MCP/CLI surface in this PR. Docs leftovers only.

…command

Three leftovers taught a command argparse no longer registers, so following the
prose landed on `invalid choice: 'alpha'`.

RELEASE.md's subcommand inventory listed `alpha` and omitted `alpha-pr`, and the
ahead-of-main paragraph credited the refusal to `release.py alpha`. Both now name
`alpha-pr` and describe opening a PR into `dev` rather than a one-step cut.

The CHANGELOG mattered more than an internal doc would: because alphas do not
stamp, the Release workflow uses `## [Unreleased]` as an alpha's GitHub Release
body, so the obsolete Added bullet would have published alongside the entry
saying that command was replaced. Rather than reconcile the two, the Added bullet
is rewritten to the `alpha-pr` -> merge -> `publish` contract and the Fixed entry
is dropped: the one-step `alpha` never appeared in a release, so no reader has
behavior to be told was fixed, and one unreleased cycle should describe its end
state rather than the path it took to get there.

Also aligned the two docstrings the review flagged: `_apply_prepare` listed
`alpha` among its callers, and `_tag_and_publish`'s summary line still claimed it
pushes the branch while its body and regression test assert tag-only.
@mocha06

mocha06 commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator Author

All three were real, and the second one was the sharpest catch. Pushed in 8fab96d.

RELEASE.md

The subcommand inventory listed alpha and omitted alpha-pr entirely, and the ahead-of-main paragraph credited the refusal to release.py alpha. Both now name alpha-pr and describe opening a PR into dev rather than a one-step cut. Verified against your "done when": no prose names a bare release.py alpha, and release.py alpha --help now returns invalid choice: 'alpha' — which is exactly why leaving those sentences in would have been worse than a stale doc.

CHANGELOG

You are right that this is not a normal docs leftover, and the reason you gave is the one that matters: because alphas do not stamp, the Release workflow uses ## [Unreleased] as the alpha's release body, so both bullets would have shipped together in the public release notes — one teaching a one-step alpha, the next saying it was replaced.

I went with the second option you offered, and a bit further. Rather than folding the obsolete description into Fixed, I rewrote the Added bullet to the alpha-pr -> merge -> publish contract and dropped the Fixed entry entirely. The reasoning: the one-step alpha never appeared in any release, so no reader has behavior to be told was fixed. Describing it as Fixed would document a bug against a version that never existed. Both bullets were one feature landing inside a single unreleased cycle, and release notes should describe that cycle's end state rather than the path it took — so ## [Unreleased] now teaches exactly one flow.

Docstrings

Both aligned, and thanks for flagging them even as non-blocking — _tag_and_publish claiming it pushed the branch was the wrong-by-one-line description of the exact bug this PR fixes, which is the kind of comment that outlives the code it describes. _apply_prepare now names prepare and release_pr (with alpha_pr delegating to the latter).

300 tests, ruff clean, lockstep verify green.

@adriannoes adriannoes left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up 8fab96d closes the docs gaps that blocked merge.

RELEASE.md now teaches alpha-pr in both the inventory and the ahead-of-main refusal, and Unreleased Added describes a single alpha-pr -> merge -> publish path (dropping the Fixed narrative that would have shipped beside it in an alpha body). Docstrings for _tag_and_publish and the _apply_prepare callers list match the tag-only / alpha_pr contract.

Approving.

For later (non-blocking)

_apply_prepare still ends its stamp note with (see ``alpha``). Worth renaming that cross-ref to alpha_pr (or dropping the see-also) in a tiny follow-up so the deleted subcommand name does not linger in the module.

@mocha06
mocha06 merged commit 2b14d30 into dev Jul 31, 2026
2 checks passed
@mocha06
mocha06 deleted the rc-dev/fix/alpha-via-pull-request branch July 31, 2026 20:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants