From 10e581398d3c7aa989ecb2c37eed91185467ff39 Mon Sep 17 00:00:00 2001 From: Thiago Gonzaga Date: Sun, 21 Jun 2026 10:47:16 -0300 Subject: [PATCH] ci(release): fail loudly on bump-PR creation instead of masking it; add RELEASING.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bump-version job ran `gh pr create` followed by `|| echo "PR already exists"`, which masked any failure (including the "Actions not permitted to create PRs" error) so the post-release pom-bump PR could be silently lost. - release.yml: branch on the gh pr create outcome — success or an already-existing PR is fine; anything else fails the job loudly. The bump branch is already pushed, so the PR can still be opened by hand. The "Allow GitHub Actions to create and approve pull requests" repo setting (now enabled) is what lets the default GITHUB_TOKEN open it. - docs/RELEASING.md: documents the release flow, the one-time setting, and the two main-ruleset caveats for merging the bump PR (close/reopen to run the required checks; squash/merge not rebase so the commit is GitHub-signed). Supersedes #88 (drops its tracking-issue fallback, unneeded now the setting is on). Fixes #11. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/release.yml | 17 ++++++++-- docs/RELEASING.md | 59 +++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 docs/RELEASING.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 459d649b..1fcd9d57 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -361,7 +361,18 @@ jobs: 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" \ + # Branch on the outcome instead of masking every failure: success or an + # already-existing PR is fine; anything else (e.g. the "Allow GitHub + # Actions to create and approve pull requests" repo setting being off) + # fails loudly so the bump is never silently lost. The branch is already + # pushed, so the PR can still be opened by hand. See docs/RELEASING.md. + if out=$(gh pr create --base main --head "$BRANCH" \ --title "chore: bump version to ${NEXT}" \ - --body "Moves main to ${NEXT} after ${TAG}." \ - || echo "PR already exists" + --body "Moves main to ${NEXT} after ${TAG}." 2>&1); then + echo "Opened bump PR: ${out}" + elif printf '%s\n' "${out}" | grep -qi 'already exists'; then + echo "Bump PR for ${BRANCH} already exists; nothing to do" + else + echo "::error::Failed to open bump PR: ${out}" + exit 1 + fi diff --git a/docs/RELEASING.md b/docs/RELEASING.md new file mode 100644 index 00000000..52849ae9 --- /dev/null +++ b/docs/RELEASING.md @@ -0,0 +1,59 @@ +# Releasing + +How a tagged release flows through CI, and how the post-release version bump +stays automated. + +## The release flow + +Releases are driven by `.github/workflows/release.yml`, triggered by pushing a +semver tag (`v[0-9]+.[0-9]+.[0-9]+`) or re-running it via `workflow_dispatch` +against an existing tag. The jobs run in order: + +1. **verify** — validates the tag is semver, checks it matches `pom.xml`, and + confirms the CI-built images for that commit already exist in GHCR. +2. **scan** — Trivy-scans both image variants, gating on CRITICAL/HIGH. +3. **promote** — retags the commit images to `:vX.Y.Z` (and `:latest` when the + tag is the highest release), signs them with cosign, and attests provenance. +4. **release** — extracts native binaries, signs the tarballs, pulls notes from + `CHANGELOG.md`, and creates the GitHub release. +5. **bump-version** — opens a PR moving `main` to the next `-SNAPSHOT` version. + +To cut a release: update `CHANGELOG.md`, set the release version in `pom.xml`, +merge, then tag the merge commit `vX.Y.Z` and push the tag. + +## Automated version bump + +After a release that updates `:latest`, the `bump-version` job pushes a +`chore/bump--SNAPSHOT` branch and opens a PR to merge it into `main` with +the default `GITHUB_TOKEN`. + +This relies on one repo setting: **Settings → Actions → General → Workflow +permissions → Allow GitHub Actions to create and approve pull requests** +(already enabled on this repo). It lets the default `GITHUB_TOKEN` open the bump +PR and stores no token or key — nothing extra for a compromised action to +exfiltrate, which is why it is preferred over a stored PAT/App-token secret. + +If `gh pr create` fails anyway (e.g. the setting was turned off), the job +**fails loudly** instead of masking the error — and because the bump branch is +already pushed, you can open the PR by hand from it. + +## 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 replays the + unsigned commit and is 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; the workflow deliberately +keeps no release secret.