Repository navigation
Description
The release PR opened immediately after a release is cut restates every earlier release's changelog, on a green run, with no error anywhere.
Observed
Cutting v0.0.2 (run 30801524556) opened PR #409 proposing 0.0.3 with a changelog containing all of 0.0.1's and 0.0.2's entries — be8517f, 8990fec, 3407288 and the rest. The version number itself (0.0.3) was correct.
Cause
Version bookkeeping is tag-independent: manifest mode reads the current version from .release-please-manifest.json, a committed file. That is why draft-until-promoted is safe for it, and #351 says so.
The changelog boundary is not tag-independent. release-please resolves "commits since the last release" to a commit SHA via the git tag. release-please-config.json sets "draft": true, and release-please.yml publishes a release only after promote has verified and retagged its image — GitHub withholds the git tag for a draft.
So the single release-please invocation that cuts vX.Y.Z also computes the next release PR, at the one moment vX.Y.Z has no tag. The boundary is unresolvable, release-please falls back to its full 500-commit search depth, and the next PR restates everything it finds.
Why it went unnoticed
- Nothing fails. The run is green, the version is right, and only the changelog text is wrong.
- It self-heals on the next push to
main, which re-grooms the PR once the tag exists. Thev0.0.1cut hit this too and was corrected by the next merge before anyone looked. - It is only visible if you read the pending release PR in the window between a release cut and the next merge.
Why it cannot be repaired by re-running
release-please.yml's grooming job is gated if: github.event_name == 'push', and the workflow_dispatch repair path requires a tag input and goes straight to promotion. There is no dispatch that re-grooms. Only a push to main fixes an affected PR.
Fix
Split the two passes around promotion, so the PR is computed after the tag exists:
release-pleasejob —skip-github-pull-request: true, release-creation only.promote— unchanged; publishing the release is what creates the tag.- new
groomjob —needs: [release-please, promote],skip-github-release: true, PR maintenance only.
Two guards on groom's condition are load-bearing and neither is the obvious default:
always() && !cancelled()—promoteis skipped on an ordinary push, and a skipped dependency would otherwise skip grooming on every non-release merge.needs.promote.result != 'failure'— a failed promotion leaves no tag, so grooming would overwrite a correct PR with the duplicated one. Leaving it untouched is the fail-closed choice.
groom mints its own App token and must keep the permission-* downscoping — omitting it mints the union of every grant the App holds rather than a narrow token, and the same App backs dependabot-lockfix.yml.
Acceptance
- Cutting a release opens a next-release PR whose changelog contains only commits after that release's tag.
- An ordinary (non-release) merge still grooms the PR.
- A failed promotion leaves the existing release PR unmodified.
Follow-up from #351.
Activity