Conversation
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review. 📝 WalkthroughWalkthroughThe release workflow validates tag ancestry and version progression. It reads POM versions through an inline Python script and updates the release tag and target branch with an atomic lease-based push. ChangesRelease safety controls
Priority: ⬇️ Low Estimated code review effort: 3 (Moderate) | ~20 minutes Change: Bug fix Merge Risk: ⚪ Minimal · up to No actionable release-workflow risk remains; the change is ready for normal merge checks. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 1 files. (1 skipped: 1 unsupported.)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Critical release-safety issues remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 4
Open (4)
What changed in this PR
This PR hardens Maven release finalization with improved tag validation, version safeguards, and atomic ref updates.
Changes:
- Removes workflow-level release concurrency.
- Adds ancestry and monotonic-version checks.
- Uses leased atomic tag and branch updates.
| File | Summary |
|---|---|
.github/workflows/release.yml |
Removes release concurrency configuration. |
.github/workflows/release-project-in-dir.yml |
Adds validation, version guards, and leased atomic pushes. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Follow-up to #3631. Remove the concurrency group. It was added to serialize releases, but the default queue keeps only one pending run: with a release running and a second pending, a third cancels the second, which would then be tagged on GitHub but never deployed or finalized - silently. The group was not load-bearing anyway, since the atomic non-forced branch push already makes a concurrent release fail visibly. Removing it trades a silent failure back for a loud one. Check the tag ancestry in publish instead of finalize-release. The check is equivalent against the branch tip, because the release commit is always a child of it, and running it before the deploy means a release cut from an unrelated commit fails while it can still be retried. Maven Central artifacts are immutable once published. Lease the tag update. The ancestry check reads the tag as fetched at checkout, but `+refs/tags/...` would then overwrite whatever the remote holds at push time. The push now leases against the OID observed during the check - the tag ref itself, not the commit it peels to - with an empty expectation when the tag did not exist. A tag moved by anyone else in between fails the push, and --atomic means the branch does not move either. Guard against a release moving the branch backwards. The next development version is derived from the released version, so releasing a tag older than the branch's own version - which means the wrong branch was selected - would set the branch to versions that are already released. Deriving is still right for the common case: releasing v5.7.0 from main on 5.6.2-SNAPSHOT must land on 5.7.1-SNAPSHOT, not go back to 5.6.2-SNAPSHOT. Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
Two review findings, both about checks running after the deploy has already
put immutable artifacts in Maven Central.
The tag ancestry check tested `refs/tags/<tag>^{commit}` for existence, so a tag
that resolves to something other than a commit looked the same as an absent tag
and skipped the check entirely - then finalize-release failed peeling it, after
publishing. Test the ref and peel it separately so that case fails loudly.
The monotonicity guard ran in finalize-release. A tag older than the branch's
development version but still an ancestor of it would pass publish, deploy, and
only then abort. Move the equivalent check - released version against the
branch's own version - into publish, ahead of the deploy. The guard in
finalize-release stays as a postcondition on the derived version.
Extract pom-version.py, now that both jobs need to read a pom version.
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
Removing it was the wrong call. Without serialization two releases cut from the same branch both pass publish and deploy different immutable versions to Maven Central; only then does the non-forced branch push fail one of them, leaving published artifacts with no release commit and the tag still on the snapshot commit. Maven Central does not allow that to be redone. The failure mode serialization does have - a third release arriving while one runs and one is pending replaces the pending one - leaves nothing published, so that release can just be re-run. A recoverable failure beats an unrecoverable one, which is the opposite of the trade described when this was removed. Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
The release jobs check out the target branch, so a script added to the repo is missing on older maintenance branches and releases from them would fail. Keep the parser in the workflow itself instead. Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
Once the artifacts are deployed, neither re-running the workflow nor re-running finalize-release can get past a push rejected by a concurrent merge. Describe how to finish the release by hand. Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
6d37eef to
c91fb9c
Compare

Follow-up to #3631, addressing review findings. v5.6.2 already released cleanly through #3631. The tag points at the
Release v5.6.2commit with a non-SNAPSHOT pom, so none of this is a live breakage.The common thread: every check that can reject a release now runs in
publish, before the deploy. Maven Central artifacts are immutable once published, so a check that fails after the deploy leaves a release that can't be retried.Keep the concurrency group, with an accurate rationale. An earlier commit on this branch removed it, because the default queue keeps only one pending run: with a release running and a second pending, a third cancels the second. That was reverted (9a246ed). Without the group, two releases from the same branch can both deploy, and then one loses the atomic branch push with immutable artifacts and no release commit. A cancelled pending run is the better worst case: it has deployed nothing and can simply be re-run. The comment in
release.ymlnow says this.Check the tag ancestry in
publish, notfinalize-release. The check is equivalent against the branch tip, since the release commit is always a child of it, and running it before the deploy means a release cut from an unrelated commit fails while it's still retryable. The tag's existence and its peeling to a commit are tested separately, so a tag that resolves to something other than a commit fails loudly instead of looking like an absent tag and skipping the check.Lease the tag update. The ancestry check reads the tag as fetched at checkout, but
+refs/tags/...would then overwrite whatever the remote holds at push time. The push now leases against the OID observed during the check (the tag ref itself, not the commit it peels to), with an empty expectation when the tag didn't exist. A tag moved by anyone else in between fails the push, and--atomicmeans the branch doesn't move either.Guard against a release moving the branch backwards. The next development version is derived from the released version, so releasing a tag older than the branch's own version would set the branch to versions already released. The guard runs in
publish, comparing the released version against the branch's development version before the deploy.finalize-releaserepeats it as a postcondition on the derived next version. This is deliberately not CodeRabbit's suggested fix of restoring the checked-out version: that breaks the common case, since releasingv5.7.0from main on5.6.2-SNAPSHOTmust land on5.7.1-SNAPSHOT, not go back to5.6.2-SNAPSHOT. A monotonicity guard covers both.5.6.2-SNAPSHOTv5.6.25.6.3-SNAPSHOT5.6.2-SNAPSHOTv5.7.05.7.1-SNAPSHOT5.6.3-SNAPSHOTv5.0.0publishKeep the pom version parser inline in the workflow. Both jobs read a pom version, so the parser is a single
POM_VERSION_PYin the workflow-levelenv, run withpython3 -c(1c19f3e). It deliberately isn't a script in the repo: the jobs check out the release branch, and maintenance branches (5.0.x,5.2.x,5.3.x) wouldn't have it.Document the manual recovery from a rejected push. The concurrency group keeps releases apart, but not a merge landing on the branch while
publishruns. That rejects the final push after the deploy, and nothing in the workflow can get past it: re-running the workflow fails inpublishon the immutable artifacts, and re-runningfinalize-releaserebuilds on the same stale commit. Both refs are left safe (branch on a SNAPSHOT version, tag where GitHub created it), so a comment on the push step (c91fb9c) now describes finishing the release by hand: tag a release commit built on the deployed commit, then bump the current branch tip to the next SNAPSHOT.Not changed
Copilot also flagged that the push uses
GITHUB_TOKEN, sosnapshot-releases.ymlwon't run for the release commits. That's accurate (it last ran on832857dfaand not on0d612f32a/70988d132), but it isn't a regression, since the previousad-m/github-push-actionused the same token, and it's arguably desirable. It also doesn't affect #3631's rationale, where the trigger was always a subsequent human merge onto a branch left at a release version.Making the workflow itself survive a concurrent merge (building the SNAPSHOT bump on the current branch tip instead of on the release commit) would remove the need for the manual recovery, but it is a larger change to the workflow and is left for a follow-up.
Testing
Run-blocks extracted from the YAML and executed verbatim against scratch repos with a stubbed
mvnwand real git 2.55.0, on the current head.Tag check in
publish:Version checks in
publish:5.6.3-SNAPSHOT:v5.6.3,v5.6.4,v5.7.0,v5.10.0,v6.0.0pass;v5.6.2,v5.0.0,v4.9.9fail.5.10.0-SNAPSHOT:v5.9.9fails,v5.10.0passes, so versions compare numerically rather than as text.v5.6.3-rc1andv5.6are rejected as not plain major.minor.patch.POM_VERSION_PYreturns5.6.3-SNAPSHOTfor both the root pom andoperator-framework-core/pom.xml, which inherits its version from the parent.Push step:
The end-to-end runs from the earlier revision (
v5.7.0from5.7.0-SNAPSHOT→ tagged5.7.0, branch5.7.1-SNAPSHOT; likewisev5.8.0andv6.0.0from5.7.3-SNAPSHOT) haven't been repeated. The later commits changed only the checks above, the parser's location, and comments.Summary by CodeRabbit
Bug Fixes
Release Process