fix(plugin-release): correct PR merge logic for BLOCKED state (#133)

* fix(plugin-release): correct PR merge logic for BLOCKED state

Prior releases failed with 'Resource not accessible by integration' when
gh pr merge was called with a branch name on a BLOCKED PR. The root cause
is that --auto requires the PR to have a pending status check that can be
satistfied by auto-merge. Without --auto, gh attempts an immediate merge
but the BLOCKED state (from branch protection requiring status checks)
causes GitHub to reject the push.

Fix: always use --auto for BLOCKED PRs, and refactor the polling loop so
it properly loops until mergeStateStatus is no longer UNKNOWN (up to 3
retries with exponential back-off) before deciding whether to use
--auto or merge directly.

Also fix the case where gh pr create is called without --json output, so
OPEN_PR is always captured correctly regardless of whether we created a
new PR or found a pre-existing one.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix: restore MERGED check and use PR number in retry loop

- Restore idempotent exit 0 when PR is already MERGED (regression from prior fix)
- Use $OPEN_PR instead of hardcoded branch name in gh pr view retry loop
- Fallback to --auto when UNKNOWN persists after exhausting retries (safe: auto-merge waits for branch protection)

Fixes bugs reported by Regression Regina on PR #133.

---------

Co-authored-by: Chris Farhood <chris@farhood.org>
Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
privilegedescalation-engineer[bot]
2026-05-04 12:13:50 +00:00
committed by GitHub
parent 490128a044
commit 73d91725a9
+38 -30
View File
@@ -334,6 +334,7 @@ jobs:
- name: Create PR for version bump
run: |
set -o pipefail
VERSION="${{ inputs.version }}"
BODY=$(printf "Automated version bump and checksum update for v%s.\n\ncc @cpfarhood" "${VERSION}")
# Create PR only if an OPEN one doesn't already exist.
@@ -345,43 +346,50 @@ jobs:
--title "release: v${VERSION}" \
--body "$BODY" \
--base main \
--head "release/v${VERSION}"
--head "release/v${VERSION}" \
--json number --jq '.number'
# Pull the number again to handle both create and pre-existing cases
OPEN_PR=$(gh pr list --base main --head "release/v${VERSION}" --state open --json number --jq '.[0].number' 2>/dev/null)
else
echo "::notice::Open PR #${OPEN_PR} for release/v${VERSION} already exists — skipping creation."
fi
# Re-fetch open PR number (handles both new and pre-existing open PRs).
PR_STATE=$(gh pr list --base main --head "release/v${VERSION}" --state open --json state --jq '.[0].state' 2>/dev/null || echo "unknown")
if [ "$PR_STATE" != "OPEN" ]; then
echo "No open PR for release/v${VERSION} — it may have already merged. Checking."
PR_STATE=$(gh pr view "release/v${VERSION}" --json state --jq '.state' 2>/dev/null || echo "unknown")
if [ "$PR_STATE" = "MERGED" ]; then
echo "PR release/v${VERSION} is already merged. Skipping merge step."
exit 0
fi
# Guard: ensure we have a PR number before proceeding
if [ -z "$OPEN_PR" ]; then
echo "::error::Could not determine PR number for release/v${VERSION}."
exit 1
fi
# Use auto-merge only when there are pending status checks to wait for.
# Valid mergeStateStatus values (gh GraphQL):
# BEHIND, BLOCKED, CLEAN, DIRTY, DRAFT, HAS_HOOKS, UNKNOWN, UNSTABLE
# Note: Field was renamed from mergeableState to mergeStateStatus in gh CLI.
MERGE_STATE=$(gh pr view "release/v${VERSION}" --json mergeStateStatus --jq '.mergeStateStatus')
if [ "$MERGE_STATE" = "BLOCKED" ]; then
echo "PR is $MERGE_STATE — enabling auto-merge."
gh pr merge "release/v${VERSION}" --auto --squash --delete-branch
elif [ "$MERGE_STATE" = "UNKNOWN" ]; then
# GitHub is still computing mergeability. Retry once after a brief wait.
echo "PR is $MERGE_STATE — GitHub is computing mergeability. Retrying in 5s."
sleep 5
MERGE_STATE=$(gh pr view "release/v${VERSION}" --json mergeStateStatus --jq '.mergeStateStatus')
if [ "$MERGE_STATE" = "BLOCKED" ]; then
echo "PR is now $MERGE_STATE — enabling auto-merge."
gh pr merge "release/v${VERSION}" --auto --squash --delete-branch
else
echo "PR is still $MERGE_STATE after retry — merging directly."
gh pr merge "release/v${VERSION}" --squash --delete-branch
echo "::notice::Working with PR #${OPEN_PR}"
# Check if PR was already merged (idempotency — safe to re-trigger after a stale branch)
MERGED_CHECK=$(gh pr view "$OPEN_PR" --json state --jq '.state' 2>/dev/null)
if [ "$MERGED_CHECK" = "MERGED" ]; then
echo "::notice::PR #${OPEN_PR} was already merged. Nothing to do."
exit 0
fi
# Determine whether to use --auto or not based on current status.
# Retry the status check up to 3 times with exponential back-off when
# GitHub is still computing the merge state (UNKNOWN state).
MAX_RETRIES=3
BACKOFF=3
MERGE_STATE=""
for i in $(seq 1 $MAX_RETRIES); do
MERGE_STATE=$(gh pr view "$OPEN_PR" --json mergeStateStatus --jq '.mergeStateStatus' 2>/dev/null)
if [ "$MERGE_STATE" != "UNKNOWN" ]; then
break
fi
if [ $i -lt $MAX_RETRIES ]; then
echo "PR merge state is UNKNOWN (GitHub still computing). Retry ${i}/${MAX_RETRIES} in ${BACKOFF}s..."
sleep $BACKOFF
BACKOFF=$((BACKOFF * 2))
fi
done
if [ "$MERGE_STATE" = "BLOCKED" ] || [ "$MERGE_STATE" = "UNKNOWN" ]; then
echo "PR is $MERGE_STATE — enabling auto-merge (safe fallback, waits for branch protection checks)."
gh pr merge "$OPEN_PR" --auto --squash --delete-branch
else
echo "PR is $MERGE_STATE — merging directly."
gh pr merge "release/v${VERSION}" --squash --delete-branch
gh pr merge "$OPEN_PR" --squash --delete-branch
fi
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}