fix(#698): sync main's auto-backmerge.yml to next (hardened admin-merge + scoped PAT) (#700)

`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 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-06-04 23:59:53 -04:00
committed by GitHub
parent 051588fa2a
commit 878e18a7e0

View File

@@ -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"