From 4e17d6ef19910e4afd4328186e553de5619dd7d1 Mon Sep 17 00:00:00 2001 From: Thiago Gonzaga Date: Sun, 14 Jun 2026 12:36:04 -0300 Subject: [PATCH] fix(release): unblock automated bump-version PR creation (#11) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bump-version job pushed chore/bump-* but `gh pr create` failed with "GitHub Actions is not permitted to create or approve pull requests (createPullRequest)", and the `|| echo "PR already exists"` masked it — so the post-release pom bump PR was silently never opened. - Stop masking gh pr create failures: distinguish outcomes — success / already-exists -> done; not-permitted -> open an idempotent tracking issue so the bump is never lost; anything else -> fail loudly. - Open the PR with the default GITHUB_TOKEN, relying on the repo setting "Allow GitHub Actions to create and approve pull requests" — no stored secret or key, so nothing extra for a compromised action to exfiltrate. When the setting is off, the tracking-issue fallback kicks in. - Skip the force-push when the remote bump branch already carries the bump, to avoid churning an open PR on release re-runs. - Pass the next version via env to versions:set (defense-in-depth). - Document the setup, the close/reopen-to-run-CI step, and squash-not-rebase (signed commits) in docs/RELEASING.md, linked from CONTRIBUTING + changelog. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/release.yml | 77 +++++++++++++++++++++++++++++++---- CHANGELOG.md | 9 ++++ CONTRIBUTING.md | 7 ++++ docs/RELEASING.md | 67 ++++++++++++++++++++++++++++++ 4 files changed, 152 insertions(+), 8 deletions(-) create mode 100644 docs/RELEASING.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3abc8b4c..0ad36704 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -311,7 +311,12 @@ jobs: permissions: contents: write pull-requests: write + issues: write steps: + # Opening the bump PR relies on the repo setting Settings → Actions → General → + # "Allow GitHub Actions to create and approve pull requests" so the default + # GITHUB_TOKEN may open it. If that setting is off, the branch is still pushed and a + # tracking issue is opened instead — the bump is never lost. See docs/RELEASING.md. - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: main @@ -342,9 +347,12 @@ jobs: fi echo "skip=false" >> "$GITHUB_OUTPUT" echo "version=${NEXT}" >> "$GITHUB_OUTPUT" - - run: ./mvnw -q versions:set -DnewVersion=${{ steps.next.outputs.version }} -DgenerateBackupPoms=false + - name: Apply next snapshot version to pom.xml if: steps.next.outputs.skip != 'true' - - name: Open bump PR + env: + NEXT: ${{ steps.next.outputs.version }} + run: ./mvnw -q versions:set -DnewVersion="$NEXT" -DgenerateBackupPoms=false + - name: Open or update bump PR if: steps.next.outputs.skip != 'true' env: GH_TOKEN: ${{ github.token }} @@ -358,10 +366,63 @@ jobs: git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" BRANCH="chore/bump-${NEXT}" + TITLE="chore: bump version to ${NEXT}" + BODY="Moves \`main\` to \`${NEXT}\` after ${TAG}." git checkout -b "$BRANCH" - git commit -am "chore: bump version to ${NEXT} after ${TAG}" - git push -f origin "$BRANCH" - gh pr create --base main --head "$BRANCH" \ - --title "chore: bump version to ${NEXT}" \ - --body "Moves main to ${NEXT} after ${TAG}." \ - || echo "PR already exists" + git commit -am "${TITLE} after ${TAG}" + # Avoid churning an already-open bump PR (and re-triggering its CI) on release + # re-runs: only force-push when the remote branch doesn't already carry this bump. + if git fetch origin "$BRANCH" 2>/dev/null && git diff --quiet FETCH_HEAD HEAD; then + echo "origin/${BRANCH} already carries this bump; not force-pushing" + else + git push -f origin "$BRANCH" + fi + + # Try to open the PR. This needs the repo setting "Allow GitHub Actions to + # create and approve pull requests" (Settings → Actions → General); without it + # the default GITHUB_TOKEN cannot open PRs. Distinguish the three outcomes + # instead of masking failures: + # - success or an existing PR -> done + # - Actions-not-permitted -> open a tracking issue (never lose the bump) + # - anything else -> fail loudly + if gh pr create --base main --head "$BRANCH" \ + --title "$TITLE" --body "$BODY" 2>pr_err.log; then + echo "Opened bump PR for ${BRANCH}" + exit 0 + fi + cat pr_err.log + + if grep -qi "already exists" pr_err.log; then + echo "Bump PR for ${BRANCH} already exists; nothing to do" + exit 0 + fi + + if ! grep -qi "not permitted to create or approve pull requests" pr_err.log; then + echo "::error::Failed to open bump PR for an unexpected reason (see log above)" + exit 1 + fi + + echo "::warning::GitHub Actions is not permitted to open PRs; opening a tracking issue instead. Enable Settings → Actions → General → \"Allow GitHub Actions to create and approve pull requests\" to let releases open the bump PR automatically. See docs/RELEASING.md." + ISSUE_TITLE="Release follow-up: open bump PR for ${NEXT}" + EXISTING=$(gh issue list --state open --limit 100 --json number,title \ + -q ".[] | select(.title == \"${ISSUE_TITLE}\") | .number" | head -1) + if [ -n "$EXISTING" ]; then + echo "Tracking issue #${EXISTING} for ${NEXT} already open; nothing to do" + exit 0 + fi + gh issue create --title "$ISSUE_TITLE" --body "$(cat <-SNAPSHOT` branch and opens a PR to merge it into `main`. + +By default, **GitHub Actions cannot open pull requests**: `gh pr create` fails +with `GitHub Actions is not permitted to create or approve pull requests +(createPullRequest)`. The job handles this gracefully — it still pushes the +branch and, when it cannot open the PR, opens a tracking issue +(`Release follow-up: open bump PR for `) so the bump is never lost. You +then open the PR manually with the command in that issue. + +### Setup — let releases open the bump PR + +In **Settings → Actions → General → Workflow permissions**, enable +**Allow GitHub Actions to create and approve pull requests**. That is the only +configuration needed: it lets the default `GITHUB_TOKEN` open the bump PR, and +it stores no token or key (nothing extra for a compromised action to exfiltrate). + +### Merging the bump PR + +The bump PR still needs a human to merge it, and two `main` ruleset constraints +shape how: + +- **CI does not start on its own.** GitHub's workflow-recursion guard suppresses + workflow runs for events the default `GITHUB_TOKEN` triggers, so the required + checks (`format`, `test`, `frontend`, `trivy`) stay pending. Re-trigger them by + **closing and reopening the PR** (preferred — it keeps your approval, whereas + pushing a commit dismisses it under `dismiss_stale_reviews_on_push`). +- **Merge with squash or a merge commit, not rebase.** The bump commit is made by + `github-actions[bot]` and is unsigned; squash/merge produce a GitHub-signed + commit that satisfies the `required_signatures` rule, while rebase would replay + the unsigned commit and be rejected. + +If you would rather the checks run automatically (no close/reopen), open the PR +with a GitHub App token or a PAT instead of the default `GITHUB_TOKEN` — a +PR authored by a non-`GITHUB_TOKEN` identity does fire `pull_request` CI. That +trades the one-time setting for a stored credential; see +[`actions/create-github-app-token`](https://github.com/actions/create-github-app-token) +and the guidance in `peter-evans/create-pull-request` / `release-please`. The +workflow does not wire this up — it deliberately keeps no release secret. + +If the setting above is left disabled, the job degrades gracefully: it pushes +the branch and opens the tracking issue, so the bump is never lost.