Auto-update PR branches after push to main #1202
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: "Auto-update PR branches" | |
| run-name: "Auto-update PR branches after push to ${{ github.ref_name }}" | |
| # When `main` advances, update every open PR that has auto-merge enabled | |
| # and is BEHIND. Without this, the strict required-status-checks policy | |
| # (`strict_required_status_checks_policy: true` on the `main` ruleset) | |
| # blocks the auto-merge button forever — auto-merge fires only when checks | |
| # are green AND the head is up-to-date with the base, but it does not | |
| # update the branch on its own. This workflow closes that gap so polish / | |
| # impl PRs squash-merge themselves end-to-end. | |
| # | |
| # Why we don't use GitHub's native merge queue: this repository is | |
| # user-owned and merge_queue rules are not available on the account's | |
| # plan — the rulesets API rejects the rule with an empty validation | |
| # error. The auto-update workflow gives us the same end-user behavior | |
| # (no manual "Update branch" clicks) without the plan dependency. | |
| on: | |
| push: | |
| branches: | |
| - main | |
| workflow_dispatch: | |
| permissions: | |
| contents: write # update-branch creates a merge commit on the head ref | |
| pull-requests: write # required to call PUT /pulls/:num/update-branch | |
| concurrency: | |
| group: auto-update-pr-branches | |
| cancel-in-progress: false | |
| jobs: | |
| update: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Wait for PR mergeable state to settle | |
| # GitHub recomputes `mergeStateStatus` asynchronously after a | |
| # push to main. If we list PRs immediately the field can still | |
| # be "UNKNOWN" — and the cached head SHA on the PR can lag | |
| # behind the actual ref, so update-branch returns "expected | |
| # head sha didn't match current head ref." Give GitHub a | |
| # moment to settle. | |
| run: sleep 30 | |
| - name: Update PRs with auto-merge enabled | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| run: | | |
| set -eo pipefail | |
| # After a push to main, every open PR with auto-merge is by | |
| # definition behind. Don't filter on `mergeStateStatus`: | |
| # right after the push it can still be UNKNOWN, and we'd | |
| # skip valid candidates. Just iterate every open | |
| # auto-merge PR — update-branch is a no-op when the head | |
| # is already up-to-date. | |
| PRS=$(gh pr list \ | |
| --repo "$GH_REPO" \ | |
| --state open \ | |
| --base main \ | |
| --limit 200 \ | |
| --json number,title,headRefName,autoMergeRequest) | |
| CANDIDATES=$(echo "$PRS" | jq -c ' | |
| [ .[] | select(.autoMergeRequest != null) ] | |
| ') | |
| COUNT=$(echo "$CANDIDATES" | jq 'length') | |
| echo "::notice::Found $COUNT open PR(s) with auto-merge enabled" | |
| if [[ "$COUNT" -eq 0 ]]; then | |
| exit 0 | |
| fi | |
| echo "$CANDIDATES" | jq -c '.[]' | while read -r pr; do | |
| NUM=$(echo "$pr" | jq -r '.number') | |
| TITLE=$(echo "$pr" | jq -r '.title') | |
| BRANCH=$(echo "$pr" | jq -r '.headRefName') | |
| echo "::notice::Updating PR #${NUM} (${BRANCH}): ${TITLE}" | |
| # Capture stderr so the actual GitHub error (conflict, | |
| # SHA mismatch, etc.) lands in the workflow log instead | |
| # of being swallowed. Failures here don't fail the job — | |
| # one stuck PR shouldn't block the others. | |
| if ! OUT=$(gh api -X PUT \ | |
| "repos/${GH_REPO}/pulls/${NUM}/update-branch" \ | |
| -H "Accept: application/vnd.github+json" 2>&1); then | |
| echo "::warning::Could not update PR #${NUM}: ${OUT}" | |
| fi | |
| done |