From 878e18a7e0d6f017eae223c118fec8eb1713309d Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Thu, 4 Jun 2026 23:59:53 -0400 Subject: [PATCH] fix(#698): sync main's auto-backmerge.yml to next (hardened admin-merge + scoped PAT) (#700) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `push: [main]` workflows execute from main's copy of the file, so the hardened auto-backmerge.yml must live on main to govern real back-merges. This brings main's copy in line with next, carrying: - #673: scoped GSD_BOT_PR_TOKEN in the branch / open-PR steps. - #698/#699: admin-merge via the PAT, force-push guarded to chore/backmerge-main-to-next-* branches, `--jq '.[0].number // empty'` + capture-from-create-URL, non-fatal labels, no greenwashing, and loud failure on a genuine conflict. Identical content to next's auto-backmerge.yml (already reviewed/merged in #699). Merging this to main triggers the hardened workflow to back-merge main → next autonomously — the end-to-end validation. Closes #698 Co-authored-by: Claude Opus 4.8 --- .github/workflows/auto-backmerge.yml | 61 ++++++++++++++-------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/.github/workflows/auto-backmerge.yml b/.github/workflows/auto-backmerge.yml index a907e7443..2c7bbe588 100644 --- a/.github/workflows/auto-backmerge.yml +++ b/.github/workflows/auto-backmerge.yml @@ -56,7 +56,7 @@ jobs: if: steps.check.outputs.next_exists == 'true' id: branch env: - GH_TOKEN: ${{ github.token }} + GH_TOKEN: ${{ secrets.GSD_BOT_PR_TOKEN || secrets.GITHUB_TOKEN }} run: | set -euo pipefail SHORT_SHA=$(git rev-parse --short HEAD) @@ -71,6 +71,10 @@ jobs: EXISTING_BR=$(echo "$EXISTING_PR" | jq -r '.headRefName // empty') if [ -n "$EXISTING_BR" ]; then + case "$EXISTING_BR" in + chore/backmerge-main-to-next-*) ;; + *) echo "::error::refusing to operate on non-backmerge branch: $EXISTING_BR"; exit 1 ;; + esac echo "Updating existing back-merge branch: $EXISTING_BR" git fetch origin "$EXISTING_BR":"$EXISTING_BR" || true git checkout "$EXISTING_BR" @@ -79,7 +83,7 @@ jobs: echo "::warning::Merge conflict back-merging main into existing back-merge branch. Human resolution required." exit 1 } - git push origin "$EXISTING_BR" + git push --force origin "$EXISTING_BR" echo "branch=$EXISTING_BR" >> "$GITHUB_OUTPUT" echo "reused=true" >> "$GITHUB_OUTPUT" else @@ -87,28 +91,19 @@ jobs: git checkout -b "$BR" next # Bring main's commits onto next via a merge commit (preserves tag history). if ! git merge --no-edit -m "chore: back-merge main into next" origin/main; then - echo "::warning::Conflict during initial back-merge main → next. Pushing the branch anyway so a maintainer can resolve via PR." - # Abort and recreate as an empty branch with a CONFLICT marker — gives the maintainer a PR to work in. + echo "::error::Cannot auto-back-merge main into next: merge conflict. A maintainer must back-merge manually (git checkout next; git merge origin/main; resolve; push)." git merge --abort || true - git push origin "$BR" - gh pr create \ - --base next \ - --head "$BR" \ - --title "chore: CONFLICT back-merging main → next (manual resolution required)" \ - --label automation \ - --label backmerge \ - --label needs-human \ - --body "main moved to ${SHORT_SHA} and cannot be auto-merged into next. Check out this branch locally, resolve the conflict, and push." - exit 0 + exit 1 fi - git push origin "$BR" + git push --force origin "$BR" echo "reused=false" >> "$GITHUB_OUTPUT" fi - name: Open or update PR - if: steps.check.outputs.next_exists == 'true' && steps.branch.outputs.reused != 'true' + if: steps.check.outputs.next_exists == 'true' + id: openpr env: - GH_TOKEN: ${{ github.token }} + GH_TOKEN: ${{ secrets.GSD_BOT_PR_TOKEN || secrets.GITHUB_TOKEN }} BR: ${{ steps.branch.outputs.branch }} run: | set -euo pipefail @@ -123,21 +118,27 @@ jobs: Generated by \`.github/workflows/auto-backmerge.yml\`. EOF ) - gh pr create \ - --base next \ - --head "$BR" \ - --title "chore: back-merge main → next (${SHORT_SHA})" \ - --label automation \ - --label backmerge \ - --body "$BODY" \ - || echo "::warning::Could not create back-merge PR (may already exist)." + PR=$(gh pr list --base next --head "$BR" --state open --json number --jq '.[0].number // empty' 2>/dev/null || echo "") + if [ -z "$PR" ]; then + PR_URL=$(gh pr create \ + --base next \ + --head "$BR" \ + --title "chore: back-merge main → next (${SHORT_SHA})" \ + --body "$BODY") + PR="${PR_URL##*/}" + fi + if [ -z "$PR" ]; then + echo "::error::back-merge PR was not created" + exit 1 + fi + gh pr edit "$PR" --add-label automation --add-label backmerge || true + echo "pr=$PR" >> "$GITHUB_OUTPUT" - - name: Enable auto-merge + - name: Admin-merge the back-merge PR if: steps.check.outputs.next_exists == 'true' env: - GH_TOKEN: ${{ github.token }} - BR: ${{ steps.branch.outputs.branch }} + GH_TOKEN: ${{ secrets.GSD_BOT_PR_TOKEN || secrets.GITHUB_TOKEN }} + PR: ${{ steps.openpr.outputs.pr }} run: | # Squash would lose the merge-commit context; use merge commit. - gh pr merge --auto --merge "$BR" \ - || echo "::warning::Could not enable auto-merge (PR may not exist yet or auto-merge is disabled on repo)." + gh pr merge --admin --merge "$PR"