Fix: Empty changelog groups, silent-skip on empty release, and publish race - #9
Merged
Merged
Conversation
…h race - changelog.groups is now empty instead of the plugin's default 6-item Keep a Changelog list, so patchChangelog no longer stubs out empty "### Added / ### Changed / ..." headers in every new Unreleased section. - Set patchEmpty = false on the patchChangelog task invoked from the release hook. Previously, if Unreleased had no content at release time, PatchChangelogTask threw StopActionException, which - since we call .run() directly instead of through Gradle's task graph - got silently swallowed, making the entire release a silent no-op (no commit, no tag, no push) while reporting BUILD SUCCESSFUL. Now it throws MissingReleaseNoteException, a real failure with a clear message. - publish-workflow.yml triggers on both the branch push and the tag push from the same release, so two Publish runs fired concurrently and both tried to `gradlew publish` the same version - one 409s. Added a check-tag step and gated the Gradle Publish step to skip on the branch-triggered run when it's also sitting on a release tag, so only the tag-triggered run publishes. Verified all three fixes locally using axion's release.localOnly mode in isolated worktrees: - groups=emptyList(): new Unreleased section has no stub headers, existing content correctly moved into the new version section. - patchEmpty=false: release now fails loudly with MissingReleaseNoteException when Unreleased is empty, instead of silently doing nothing.
There was a problem hiding this comment.
Pull request overview
This PR improves the release/publish automation around changelog patching and GitHub Packages publishing, addressing empty changelog group stubs, silent release skipping when Unreleased is empty, and duplicate publish attempts from concurrent workflow triggers.
Changes:
- Stop pre-populating new Unreleased sections with default Keep a Changelog group headers by setting
changelog.groupsto empty. - Make the release hook fail loudly when Unreleased has no content by setting
patchEmpty = falseon the invokedpatchChangelogtask. - Add tag-detection and gating in the publish workflow to avoid duplicate publishes when both branch and tag pushes trigger concurrently.
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| CHANGELOG.md | Updates Unreleased notes to reflect the release/publish automation fixes. |
| build.gradle.kts | Adjusts changelog plugin configuration and makes empty-Unreleased releases fail loudly. |
| .github/workflows/publish-workflow.yml | Attempts to prevent duplicate publishes by skipping publish on the branch-triggered run when a release tag is present. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
16
to
20
| - uses: actions/checkout@v7 | ||
| with: | ||
| fetch-depth: 0 | ||
| fetch-tags: true | ||
| - uses: gradle/actions/setup-gradle@v6 |
tajobe
added a commit
that referenced
this pull request
Jul 22, 2026
…h race (#9) - changelog.groups is now empty instead of the plugin's default 6-item Keep a Changelog list, so patchChangelog no longer stubs out empty "### Added / ### Changed / ..." headers in every new Unreleased section. - Set patchEmpty = false on the patchChangelog task invoked from the release hook. Previously, if Unreleased had no content at release time, PatchChangelogTask threw StopActionException, which - since we call .run() directly instead of through Gradle's task graph - got silently swallowed, making the entire release a silent no-op (no commit, no tag, no push) while reporting BUILD SUCCESSFUL. Now it throws MissingReleaseNoteException, a real failure with a clear message. - publish-workflow.yml triggers on both the branch push and the tag push from the same release, so two Publish runs fired concurrently and both tried to `gradlew publish` the same version - one 409s. Added a check-tag step and gated the Gradle Publish step to skip on the branch-triggered run when it's also sitting on a release tag, so only the tag-triggered run publishes. Verified all three fixes locally using axion's release.localOnly mode in isolated worktrees: - groups=emptyList(): new Unreleased section has no stub headers, existing content correctly moved into the new version section. - patchEmpty=false: release now fails loudly with MissingReleaseNoteException when Unreleased is empty, instead of silently doing nothing.
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.
Summary
changelog.groupsis now empty instead of the plugin's default 6-item Keep a Changelog list, sopatchChangelogno longer stubs out empty### Added / ### Changed / ...headers in every new Unreleased section.patchEmpty = falseon thepatchChangelogtask invoked from the release hook. Previously, if Unreleased had no content at release time,PatchChangelogTaskthrewStopActionException, which — since we call.run()directly instead of through Gradle's task graph — got silently swallowed, making the entire release a silent no-op (no commit, no tag, no push) while reportingBUILD SUCCESSFUL. Now it throwsMissingReleaseNoteException, a real failure with a clear message.publish-workflow.ymltriggers on both the branch push and the tag push from the same release, so twoPublishruns fired concurrently and both tried togradlew publishthe same version — one 409s. Added a check-tag step and gated theGradle Publishstep to skip on the branch-triggered run when it's also sitting on a release tag, so only the tag-triggered run publishes.Test plan
./gradlew clean buildpassesgroups = emptyList()in an isolated worktree viarelease.localOnly: the new Unreleased section has no stub headers, and existing Unreleased content correctly moved into the new version section (confirmed viagit showdiff on the resulting local commit)patchEmpty = falsein an isolated worktree: with an empty Unreleased section, the release now fails loudly withMissingReleaseNoteExceptioninstead of silently producing no commit/tag/push while reporting successpublish-workflow.ymlYAML syntax validated; theif:condition logic matches the described skip case (branch run + on a release tag) directly