feat: implement release-branch workflow#1076
Conversation
Replaces the cut-from-main release flow with long-lived release/vX.Y branches carrying rcs and finals. main carries X.Y.0.devN for the next minor. Patches cherry-pick onto the existing release branch. Adds four workflow_dispatch workflows: cut-release-branch, publish-release (was cd.yml), cherry-pick-to-release, publish-dev-from-main. Adds bump_version.py with five PEP 440 transition modes plus unit tests. Prerelease publishing to PyPI is gated on PUBLISH_PRERELEASES (default false). Auth migrates from the mellea-auto-release GitHub App to GITHUB_TOKEN with inline permissions blocks. See RELEASE.md for the full operator-facing flow. Assisted-by: Claude Code Signed-off-by: Alex Bozarth <ajbozart@us.ibm.com>
|
Stepping back to the original problem we had on the last release, where we had to basically pause new PR merges for a few days while the release was prepared, I think the main thing we want from a release branch is to let contributors keep merging to main while a release is being stabilized. With that in mind, I want to push back a bit on the scope of this PR. I think most of the machinery here is solving an adjacent problem (formal PEP 440 rc/dev/patch lifecycle) rather than the merges-during-stabilization problem we ran into last time. I think we could actually solve this with a simpler flow:
I'd suggest splitting this in two: one PR for the release branch and a separate issue/PR for the PEP 440 flow. I think that would allow for more discussion but also let us close on the merge-during-release problem quicker. |
- cherry_pick_to_release.sh: enable pipefail so pipeline failures are not swallowed - release.sh: enable -u and pipefail; reuse existing changelog-sync branch on retry instead of force-recreating it - bump_version.py: override inherited UV_FROZEN=1 so uv lock can update the lockfile after a version bump - pypi.yml: read version from pyproject.toml so the prerelease gate works on manual workflow_dispatch (where github.ref_name is the branch, not the tag) Assisted-by: Claude Code Signed-off-by: Alex Bozarth <ajbozart@us.ibm.com>
|
I addressed @planetf1 technical review with a new commit and a response, as for @psschwei and @jakelorocco your review is the type of feedback that I wanted to drive the discussion at sync on Monday, in fact I had meant to open this as draft to make it clear this was just a proposal. In writing this I focused on making it full-featured so we could remove undesired features afterwards rather than needing to rework new functionally in, so I'm open to removing things to streamline it. As such I'll have Claude detail out some refactoring ideas for discussion based on your comments and my opinions, we can then dig into these ideas both here and on Monday's call: Refactor ideas for MondayFour points to discuss. Items 1 and 2 are alternatives — pick at most one. Item 3 is orthogonal to both. Item 4 is the "split into multiple PRs" question. My current lean on each is in italics. 1. Drop speculative prerelease tagging. Currently we create git tags for every rc and dev (
2. Drop prereleases entirely. A more aggressive trim that supersedes #1: stabilization happens in-place on the release branch with no rc cycle, no 3. Drop cherry-pick, switch to PRs targeting the release branch. This is Paul's proposal and is orthogonal to #1 and #2. The current PR's flow is "every change lands on main first; maintainers run a cherry-pick workflow to port selected commits onto the release branch." Paul's alternative: contributors open stabilization PRs directly against What gets removed if we switch: Tradeoffs:
I'd lean toward keeping cherry-pick. "Main is the source of truth" matches what most contributors already do, and the cherry-pick machinery is written and validated. The simplification Paul gets is real but not large. 4. Split into two PRs. @psschwei suggested splitting release-branch + cherry-pick into PR1 and the PEP 440 lifecycle into PR2. I'd push back on this. If we decide to keep prerelease versioning, it should land integrated, not split — and if we decide to remove it (per #2 above), there's nothing to split. Splitting creates an awkward intermediate state where the project has half a release model. For illustration, the cleanest seam would be:
The seam isn't clean — both PRs edit The smaller-review-surface benefit Paul wants from a split, we can also get by trimming features inside this PR after Monday's discussion — without the integration churn. |
The context we provide the models is going to matter here. For example, if you give it this for a prompt will give you a response that is much more in favor of splitting and just merging the release branch part. |
But this goes against the whole point of having a release branch. A release branch's job is to freeze a code shape so you can stabilize it without main's churn leaking in. Cherry-pick inverts that: it makes main the place where fixes are authored, which means fixes get authored against main's current shape, which means main's churn does leak in. |
I did say it was Claude outlining my opinions, but perhaps I should have been clearer.
Honestly I don't disagree with you, but the current cherry-pick model was what we outlined and decided on in the design call last week, thus why Claude was so insistent on it, it saw it as a design requirement, not a choice made during implementation. I am fully ok with dropping the whole cheery-pick code and just using PRs. Its the easiest update of the items above |
planetf1
left a comment
There was a problem hiding this comment.
Five WARNINGs from a deeper pass focused on retry / idempotency and CI plumbing. None are architectural; all are small fixes. Suggestion blocks attached for each.
planetf1
left a comment
There was a problem hiding this comment.
Following up on the inline WARNINGs review with one more SUGGESTION on workflow concurrency. Escalating to REQUEST_CHANGES because W1, W6, and W7 either break or weaken documented behaviour (retry path for finals, prerelease retry, and CI on the sync PR) — would prefer to see those addressed (or explicitly deferred to a follow-up issue) before merge. Architecture and direction look good; this is about the rough edges around recovery and CI plumbing.
|
Ok to summarize decisions made in todays call based on my suggestions above:
We will be moving the git tagging behind the
Complete removal of the cherry-pick workflow and script. cherry-picking will be done manually and merged via PRs against the release branch. So if a change is needed on the release branch the author will need to create a followup PR to also merge it on main (usually after the PR is merged on the release brach). Though this could be done in the opposite direct depending on the situation. In addition we'll leave the current workflow env in place for now and can revisit removing it or expanding it's members in settings at a later time |
Drop the cherry-pick workflow and script in favor of manual followup PRs (release-branch fixes get a separate PR back to main). Gate prerelease tagging, GitHub Release creation, and PyPI upload behind the existing PUBLISH_PRERELEASES repo variable; with the default (false), the version bump commit is the only artifact for prereleases. Also addresses prior review on retry idempotency and CI plumbing. Cherry-pick removal: - Delete cherry_pick_to_release.sh and cherry-pick-to-release.yml - RELEASE.md: backports become a manual followup-PR flow - ci.yml: dispatch comment now points to release.sh sync-PR use Prerelease handling gated on PUBLISH_PRERELEASES: - Tag, prerelease GitHub Release, and PyPI upload only when the flag is true; otherwise the version-bump commit is the only artifact - Incremental notes: rc2 diffs against rc1 so testers see "what's new in this rc"; the cumulative view shows up on the final's Release START_TAG selection for --notes-start-tag: - Prereleases: most recent reachable tag (incremental) - Finals: previous final by version shape — git describe excluding rc/dev for patches, version-pattern lookup for minors (parallel release branches aren't reachable, empty for X.0.0 (gh's default fills in) Idempotency for bump_type=none retry path: - gh release create / git tag / git commit / sync PR creation all guarded against repeat runs; retry skips what's done, finishes what isn't Other CI plumbing fixes: - release.sh: explicit gh workflow run ci.yml after sync PR - docs-publish.yml: latest_check is continue-on-error and the deploy gate fails open on missing output - cut-release-branch.yml: concurrency: release group added Assisted-by: Claude Code EOF ) Signed-off-by: Alex Bozarth <ajbozart@us.ibm.com>
|
Ok, all of @planetf1 review comments and the follow up items from today's call should be pushed now. If everyone could please review both the implementation as well as the documentation to make sure I caught everything. |
Lead with the cut/stabilize/promote sequence; move versioning, PUBLISH_PRERELEASES, workflows, branch protection, retention, and docs-publish behavior into an appendix. Add a note on the manual prep for major version bumps. Assisted-by: Claude Code Signed-off-by: Alex Bozarth <ajbozart@us.ibm.com>
The cd.yml workflow was renamed to publish-release in this branch, and the release flow is workflow_dispatch-only rather than continuous. Use "release publishing" and "publish-release" directly. Assisted-by: Claude Code Signed-off-by: Alex Bozarth <ajbozart@us.ibm.com>
|
another more general comment, though possibly a tangent from this PR: would doing more deliberate sprint planning help at all with the release branch issues? in other words, if we had a clear set of these are the things we are delivering, to what extent could that alleviate some of the issues we may run into in the future with cherry picks, etc. ? |
|
@psschwei more planning could reduce unexpected last minute impacts - work we'd missed, not validated, external dependencies. This may reduce the need for cherry-picks, but this is in part a technical discussion where if, after we've branched for the release, we find an issue how we resolve it. To some extent it might already be regarded as a process escape if it gets to this point -- but there'll always be exceptions we need to manage, and the concept of a release branch helps in providing some isolation to work those things out without impacting ongoing development, or indeed the release. The cherry-pick discussion was trying to formalize - set a pattern - for a best practice way to ensure code changes don't get made to the release, and then forgotten to be added to main, and to reduce the overhead of multiple PRs. But since each issue may differ we could have problems specific to the actual release branch (timing, versions) that don't apply to main. I'd therefore see using cherry-pick as a mechanism to keep the same commits a good best practice where it can be applied, but it won't always. As to whether we do a PR or direct push to release branches - I'd err on PR as it's clearer and consistent with what we do with main, and the overhead is small. So with the mechanism done, we should always ask ourselves how we could improve the process if we get to the point where a release is 'bad'? |
|
@planetf1 I think we may be talking about two different things. I agree we need a method to bring bring changes from release branches into main. And I see that what I was referring to above as cherry-pick might not be clear: I meant the workflow in the PR, not the git cherry-pick mechanism. Sorry for the impreciseness. I was thinking about it from a slightly different angle (one which sort of came up yesterday): We have a patch release scheduled for Tuesday. There's also work on Feature A that is in progress (some has been merged, some hasn't). Would it be better to wait to merge all of Feature A at once post-patch, or do it in stages? In other words, do we schedule merging of all Feature A PRs on Wednesday rather than when they're ready? I'm not saying we should do that. I'm just wondering if that is also something we need to factor in to our release process. (And it's fine to say no we don't too, I don't think we do, but figured I'd at least bring it in case others had different opinions) |
The 0.6.0.dev0 marker on this branch became stale once 0.6.0 shipped to PyPI. Advance to 0.7.0.dev0 so the next release cut produces 0.7.0rc0. Assisted-by: Claude Code Signed-off-by: Alex Bozarth <ajbozart@us.ibm.com>
|
Just used an update since we released 0.6.0 instead of 0.5.1 like we planned. This is good to merge as is now. |
planetf1
left a comment
There was a problem hiding this comment.
good to go ahead and continue refinement in future prs as needed
|
@psschwei on features I would say that generally they should be in the code before we branch, or they don't make that release (or we delay the release). I'd expect fixes/patchups to go into the release branch only. There's always exceptions though and I'd say then it becomes a case by case basis As we develop on 'main' we expect to keep things working, not regress, and be in a always read to release state (yes, I know, things happen... reasons..). So if 'part' of a feature goes in, it should be suitably self-contained and coherent. It need not be complete -- but any docs/api refs that exist should be accurate (we may opt to leave them out if they don't make sense). Anything that has interdependencies within the project or outside should fail-safe/not regress/be behind a feature flag. We're on 0.x so won't have all of this or get it right yet, but I think that's the vision I'd have of where we should get to. |
I agree with this. The other option we have for work that partially gets in but isn't ready for wide consumption is feature flags, though that comes with its own challenges too. But we can cross those bridges if/when they appear on the horizon. |
|
@jakelorocco this PR is good to merge, just want to get an final approval from you (and @psschwei and @nrfulton ) before merging |
jakelorocco
left a comment
There was a problem hiding this comment.
lgtm; left comments with questions / concerns that shouldn't be blocking; thank you for revamping this!
psschwei
left a comment
There was a problem hiding this comment.
I'm only focusing on the RELEASE.md doc, so this is mostly a matter of how things are documented in that file (I'm fine with the general machinery of the PR). If I'm doing a release, I want the release doc to be basically a step-by-step of: do this, then do that, set this, etc.
So I think it would be helpful to have this doc go almost full ELI5 with how it's set up:
Major sections
- minor release
- patch release
- troubleshooting
- appendix
and then for each section, something along these lines (using the minor release as an example):
## Making a Minor Release
- [Create a release branch](#create-a-release-branch)
- [Stabilize release branch](#stabilize-release-branch)
- ...
### Create a release branch
- Run [Cut Release Branch](.github/workflows/cut-release-branch.yml) action
* OPTIONAL: Set `confirm_minor` ...
### Stabilize Release branch
- ...
### Promote to final
- ...
### Bring changes back to main
- ... A lot of the details on what we're doing could go in the appendix (or maybe better as comments in the workflow, so they could be the source of truth, and then we could just link out if someone wants to understand them).
The helper was only used twice; the other three subprocess.run calls need capture_output or a custom env, so the helper couldn't reduce those. Drop the indirection and inline. Assisted-by: Claude Code Signed-off-by: Alex Bozarth <ajbozart@us.ibm.com>
Reorganized into minor release / patch release / troubleshooting / appendix sections with per-step subsections, per Paul's review feedback. Stripped workflow internals from the body and pulled rc cycling, the major-bump corner case, retry/rollback, and the ad-hoc dev publish into appendix or troubleshooting. Action links now point to the workflow run pages rather than the YAML source. Assisted-by: Claude Code Signed-off-by: Alex Bozarth <ajbozart@us.ibm.com>
|
@psschwei I've done a complete refractor of the doc based on your above comments. If you could re-review it and double check it properly addressed all your comments and resolve them. |
psschwei
left a comment
There was a problem hiding this comment.
Small non-blocking nits around formatting, but otherwise LGTM (and much easier to follow how to do a release, future us will be very happy when we go through the process 😄 )
|
also @jakelorocco @ajbozarth remind me -- with this new workflow, we are NOT going to be doing weekly patch releases, but only on an as-needed basis (which we are anticipating will be few and far between for the time being)... is that right? just trying to make sure I remember the details |
to my understanding this is correct |
Per Paul's nit: TOC entries are numbered, so the heading text should match. Numbered the H3s under Making a minor release and Making a patch release, plus updated cross-references in the rc cycling appendix. Assisted-by: Claude Code Signed-off-by: Alex Bozarth <ajbozart@us.ibm.com>
fixed |
|
Unless there's any further comments I'll merge this at my EOD |
Misc PR
Type of PR
Description
Replaces the cut-from-main release flow with a release-branch model. Every minor release gets a long-lived
release/vX.Ybranch carrying rcs and the final;maincarriesX.Y.0.devNfor the next minor. Patches land on the release branch via PRs targeting that branch and go through their own rc cycle; if a fix also belongs onmain, the author opens a separate followup PR. SeeRELEASE.mdfor the full operator-facing documentation.Adds three
workflow_dispatchworkflows (cut-release-branch,publish-release(renamed fromcd.yml),publish-dev-from-main), abump_version.pyhelper with five PEP 440 transition modes, and aPUBLISH_PRERELEASESrepo variable that gates prerelease tagging, GitHub Release creation, and PyPI uploads for rc/dev versions. Auth migrates from themellea-auto-releaseGitHub App toGITHUB_TOKENwith inlinepermissions:blocks.Testing
End-to-end dry-run validated on
ajbozarth/melleafork: cut-release, publish-dev-from-main, rc, final, and the explicit downstream-workflow dispatches (pypi.yml,docs-publish.yml,ci.yml) that work around GitHub's anti-loop rule forGITHUB_TOKEN-authored events.Attribution
Admin followups (post-merge)
The release-branch workflow needs the following one-time configuration in repo settings before it can run end-to-end. None of these block the merge — they're prerequisites for the first dispatch of
cut-release-branchandpublish-release.mainbranch ruleset: addgithub-actions[bot]as a bypass actor socut-release-branchandpublish-dev-from-maincan push their version-bump commits directly.release/**branch ruleset: create a ruleset matchingrelease/**that mirrorsmain's protections (PR review required, status checks required, no force-push, no deletion), and addgithub-actions[bot]as a bypass actor sopublish-releasecan push the version bump and the changelog commit.PUBLISH_PRERELEASESrepo variable: create under Settings → Secrets and variables → Actions → Variables, default valuefalse. This is the forward-looking flag for publishing public prerelease artifacts; leave atfalsefor now.