From cf41269a6dc020a8797cfeee1b6fb98c94a7c496 Mon Sep 17 00:00:00 2001 From: Markus Neusinger <2921697+MarkusNeusinger@users.noreply.github.com> Date: Thu, 7 May 2026 22:22:54 +0200 Subject: [PATCH] ci(auto-update): wait for state to settle and surface real errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first run after #5957 merged found 0 BEHIND PRs even though three PRs were stuck behind main. Two issues: 1. The workflow runs ~4s after the push to main, but GitHub recomputes `mergeStateStatus` and the cached PR head SHA asynchronously. Right after the push the field can still be "UNKNOWN" and the cached head can be stale. Add a 30s sleep at the start so GitHub has time to settle. 2. The script filtered for `mergeStateStatus == "BEHIND"` and skipped UNKNOWN candidates — exactly the ones we wanted to fix. Drop the filter: after a push to main, every open auto-merge PR is by definition behind, and update-branch is a no-op when the head is already up-to-date. Also: - Bump permissions to `contents: write` since update-branch creates a merge commit on the head ref. - Drop `--silent` and capture stderr so the actual GitHub error (conflict, SHA mismatch, etc.) lands in the workflow log instead of the generic "likely conflict or stale ref" guess. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/auto-update-pr-branches.yml | 49 +++++++++++-------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/.github/workflows/auto-update-pr-branches.yml b/.github/workflows/auto-update-pr-branches.yml index 785562bbe3..6327e35d1f 100644 --- a/.github/workflows/auto-update-pr-branches.yml +++ b/.github/workflows/auto-update-pr-branches.yml @@ -22,7 +22,7 @@ on: workflow_dispatch: permissions: - contents: read # required for actions/checkout (not strictly needed here, but cheap) + contents: write # update-branch creates a merge commit on the head ref pull-requests: write # required to call PUT /pulls/:num/update-branch concurrency: @@ -33,50 +33,59 @@ jobs: update: runs-on: ubuntu-latest steps: - - name: Update behind PRs with auto-merge enabled + - 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 - # Pull every open PR targeting main that has auto-merge enabled. - # `mergeStateStatus` is the field that surfaces "BEHIND" — the - # exact state we want to fix. Skip everything else (CLEAN, - # BLOCKED for missing reviews, DIRTY for conflicts, etc.). + # 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,mergeStateStatus,autoMergeRequest) + --json number,title,headRefName,autoMergeRequest) - BEHIND=$(echo "$PRS" | jq -c ' - [ .[] | select(.autoMergeRequest != null and .mergeStateStatus == "BEHIND") ] + CANDIDATES=$(echo "$PRS" | jq -c ' + [ .[] | select(.autoMergeRequest != null) ] ') - COUNT=$(echo "$BEHIND" | jq 'length') - echo "::notice::Found $COUNT PR(s) BEHIND with auto-merge enabled" + 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 "$BEHIND" | jq -c '.[]' | while read -r pr; do + 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}" - # update-branch merges the base ref into the head ref; this - # is what the "Update branch" button does in the UI. - # Failures here (e.g. merge conflict, branch deleted) are - # logged as a warning but don't fail the whole workflow — + # 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 ! gh api -X PUT \ + if ! OUT=$(gh api -X PUT \ "repos/${GH_REPO}/pulls/${NUM}/update-branch" \ - -H "Accept: application/vnd.github+json" \ - --silent 2>&1; then - echo "::warning::Could not update PR #${NUM} (likely conflict or stale ref)" + -H "Accept: application/vnd.github+json" 2>&1); then + echo "::warning::Could not update PR #${NUM}: ${OUT}" fi done