From 8ec0891b6ea2cabeadf03ce43dc480ce40a6ea4a Mon Sep 17 00:00:00 2001 From: Jose Armesto Date: Wed, 27 May 2026 17:29:40 +0200 Subject: [PATCH 1/3] feat(release): add auto-merge-level input for release-please PRs Add an `auto-merge-level` input (none, patch, minor, major; default none) to the release-please reusable workflow. When set, the workflow enables GitHub auto-merge (`gh pr merge --auto --squash`) on the open release-please PR when the PR's bump is no larger than the configured ceiling, so it merges once CI passes. The bump is derived from `.release-please-manifest.json` (next version on the PR head branch vs. current version on the base branch), so it is independent of each caller's PR-title configuration. Auto-merge is reconciled on every run: if a PR's bump grows past the ceiling before it is merged, auto-merge is disabled again. The merge runs with the release-please GitHub App token, so it is attributed to the App. The App must be granted bypass on branch protection and the repo must have "Allow auto-merge" enabled. --- .github/workflows/release.yaml | 80 ++++++++++++++++++++++++++++++++++ CHANGELOG.md | 6 ++- 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index d832abd..2217f4f 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -7,6 +7,12 @@ name: Release on: workflow_call: + inputs: + auto-merge-level: + description: "Automatically merge the Release Please PR when CI passes, up to this bump level. One of: none, patch, minor, major." + type: string + required: false + default: none secrets: RELEASE_PLEASE_CLIENT_ID: description: Client ID for create-github-app-token @@ -65,3 +71,77 @@ jobs: token: ${{ steps.generate_token.outputs.token }} config-file: release-please-config.json manifest-file: .release-please-manifest.json + + # Reconcile auto-merge on the open Release Please PR every run. release-please + # keeps a single PR open and rewrites it as commits land, so its bump level can + # change over time; we enable or disable auto-merge to match the current bump. + # The bump is derived from .release-please-manifest.json (next version on the PR + # head branch vs. current version on the base branch), which is authoritative + # regardless of how each caller configures its PR title. + - name: Reconcile auto-merge on the Release Please PR + if: ${{ steps.release_please.outputs.pr != '' }} + env: + GH_TOKEN: ${{ steps.generate_token.outputs.token }} + GH_REPO: ${{ github.repository }} + AUTO_MERGE_LEVEL: ${{ inputs.auto-merge-level }} + PR_JSON: ${{ steps.release_please.outputs.pr }} + run: | + set -euo pipefail + + level_num() { + case "$1" in + none) echo 0 ;; + patch) echo 1 ;; + minor) echo 2 ;; + major) echo 3 ;; + *) echo "Invalid level '$1' (expected none, patch, minor or major)" >&2; exit 1 ;; + esac + } + # Fail fast on an invalid auto-merge-level input. + level_num "$AUTO_MERGE_LEVEL" >/dev/null + + pr_number=$(jq -r '.number' <<<"$PR_JSON") + head_branch=$(jq -r '.headBranchName' <<<"$PR_JSON") + base_branch=$(jq -r '.baseBranchName' <<<"$PR_JSON") + + # Read the single root (".") entry of the release-please manifest at a ref. + manifest_version() { + gh api "repos/${GH_REPO}/contents/.release-please-manifest.json?ref=$1" \ + -H "Accept: application/vnd.github.raw" | jq -r '.["."]' + } + + current=$(manifest_version "$base_branch") + next=$(manifest_version "$head_branch") + + IFS=. read -r cmaj cmin cpat <<<"$current" + IFS=. read -r nmaj nmin npat <<<"$next" + + if [ "$nmaj" -gt "$cmaj" ]; then bump=major + elif [ "$nmin" -gt "$cmin" ]; then bump=minor + elif [ "$npat" -gt "$cpat" ]; then bump=patch + else bump=none + fi + + echo "Release PR #${pr_number}: ${current} -> ${next} (${bump} bump); auto-merge-level=${AUTO_MERGE_LEVEL}" + + # Merge when the PR's bump is a real release and is no larger than the + # configured ceiling (none < patch < minor < major). + if [ "$(level_num "$bump")" -ge 1 ] && [ "$(level_num "$bump")" -le "$(level_num "$AUTO_MERGE_LEVEL")" ]; then + want_merge=true + else + want_merge=false + fi + + # Only mutate when the desired state differs from the current one, so the + # common case (no opt-in) is a no-op rather than a failing disable call. + enabled=$(gh pr view "$pr_number" --json autoMergeRequest --jq '.autoMergeRequest != null') + + if [ "$want_merge" = true ] && [ "$enabled" != true ]; then + echo "Enabling auto-merge (squash) on PR #${pr_number}" + gh pr merge "$pr_number" --auto --squash + elif [ "$want_merge" = false ] && [ "$enabled" = true ]; then + echo "Disabling auto-merge on PR #${pr_number}" + gh pr merge "$pr_number" --disable-auto + else + echo "Auto-merge already in desired state (enabled=${enabled}, want=${want_merge}); nothing to do" + fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f201fd..60e41e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), however this project does not use Semantic Versioning and there are no releases. Instead this file uses a date-based structure. -## 2026-05-21 +## 2026-05-27 + +### Added + +- `release.yaml` (release-please reusable workflow) gained an `auto-merge-level` input (`none`, `patch`, `minor`, `major`; default `none`). When set, the workflow enables GitHub auto-merge (`gh pr merge --auto --squash`) on the open release-please PR if the PR's bump is no larger than the configured ceiling, so the PR merges automatically once CI passes. The bump is derived from `.release-please-manifest.json` (next version on the PR head branch vs. current version on the base branch), so it is independent of each caller's PR-title configuration. Auto-merge is reconciled on every run — if a release-please PR's bump grows past the ceiling (e.g. from `patch` to `major`) before it is merged, auto-merge is disabled again. The default `none` preserves the previous behaviour (no auto-merge). The merge is performed with the `release-please` GitHub App token, so the merge is attributed to the App; the App must be granted bypass on any branch protection and the repository must have "Allow auto-merge" enabled. ### Changed From af939750f66ee00a302289b9b37632dd37bf4e64 Mon Sep 17 00:00:00 2001 From: Jose Armesto Date: Wed, 27 May 2026 17:52:41 +0200 Subject: [PATCH 2/3] refactor(release): warn instead of fail when auto-merge can't be enabled If a consuming repo hasn't enabled "Allow auto-merge" or hasn't granted the release-please App branch-protection bypass, enabling auto-merge on the release PR should not fail the whole release workflow run. Soft-fail the `gh pr merge --auto` call with a ::warning:: annotation instead, so the misconfiguration is surfaced without breaking the release. --- .github/workflows/release.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 2217f4f..d07c854 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -138,7 +138,10 @@ jobs: if [ "$want_merge" = true ] && [ "$enabled" != true ]; then echo "Enabling auto-merge (squash) on PR #${pr_number}" - gh pr merge "$pr_number" --auto --squash + # Soft-fail: a repo that hasn't enabled "Allow auto-merge" or hasn't granted + # the App branch-protection bypass shouldn't break its whole release run. + gh pr merge "$pr_number" --auto --squash \ + || echo "::warning::Could not enable auto-merge on PR #${pr_number}. Check that 'Allow auto-merge' is enabled and the release-please App can bypass branch protection." elif [ "$want_merge" = false ] && [ "$enabled" = true ]; then echo "Disabling auto-merge on PR #${pr_number}" gh pr merge "$pr_number" --disable-auto From 8a913abd3f69b341ee99789f56ea9d7c43d9759f Mon Sep 17 00:00:00 2001 From: Jose Armesto Date: Wed, 27 May 2026 18:09:13 +0200 Subject: [PATCH 3/3] fix(release): soft-fail auto-merge disable, skip reconcile when level is none Address two code-review findings on the auto-merge reconcile step: - Soft-fail the `gh pr merge --disable-auto` call with a ::warning:: annotation, symmetrically with the enable path, so a transient API error or a race doesn't hard-fail the release run. - Guard the step with `inputs.auto-merge-level \!= 'none'`, so the default `none` is a true no-op and never disables an auto-merge that was enabled outside this workflow. --- .github/workflows/release.yaml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index d07c854..9930d7d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -79,7 +79,7 @@ jobs: # head branch vs. current version on the base branch), which is authoritative # regardless of how each caller configures its PR title. - name: Reconcile auto-merge on the Release Please PR - if: ${{ steps.release_please.outputs.pr != '' }} + if: ${{ steps.release_please.outputs.pr != '' && inputs.auto-merge-level != 'none' }} env: GH_TOKEN: ${{ steps.generate_token.outputs.token }} GH_REPO: ${{ github.repository }} @@ -132,8 +132,8 @@ jobs: want_merge=false fi - # Only mutate when the desired state differs from the current one, so the - # common case (no opt-in) is a no-op rather than a failing disable call. + # Only mutate when the desired state differs from the current one, to avoid + # redundant enable/disable calls (and the errors a redundant one can raise). enabled=$(gh pr view "$pr_number" --json autoMergeRequest --jq '.autoMergeRequest != null') if [ "$want_merge" = true ] && [ "$enabled" != true ]; then @@ -144,7 +144,10 @@ jobs: || echo "::warning::Could not enable auto-merge on PR #${pr_number}. Check that 'Allow auto-merge' is enabled and the release-please App can bypass branch protection." elif [ "$want_merge" = false ] && [ "$enabled" = true ]; then echo "Disabling auto-merge on PR #${pr_number}" - gh pr merge "$pr_number" --disable-auto + # Soft-fail symmetrically with the enable path: a transient API error or a + # race shouldn't break the release run. + gh pr merge "$pr_number" --disable-auto \ + || echo "::warning::Could not disable auto-merge on PR #${pr_number}." else echo "Auto-merge already in desired state (enabled=${enabled}, want=${want_merge}); nothing to do" fi