Unraveling GitHub PR Merge Blocks: A Software Engineering Tool Deep Dive
The GitHub Conundrum: When Checks Pass, But Merges Don't
Imagine this scenario: you've pushed a change to your feature branch, your GitHub Actions workflow runs, and gh pr checks --required confidently reports "PASS." All green! You're ready to merge, but then, GitHub's UI or GraphQL's mergeStateStatus insists your Pull Request (PR) is "BLOCKED." Frustrating, right? This isn't a rare glitch; it's a specific interaction within GitHub's branch protection rules and CI/CD check evaluations, particularly when dealing with concurrent workflow runs on the same head SHA.
Understanding the Discrepancy in a Key Software Engineering Tool
The core of the problem lies in how GitHub evaluates required checks for display versus for actual mergeability. As detailed in a recent community discussion, the required-check projection (what gh pr checks shows) often reads the "best completed check runs" for a given head SHA. This means if one of your concurrent workflow runs completes successfully, that's what gets reported as PASS.
However, the merge gate—the actual mechanism that determines if a PR can be merged—reads the "check suite's aggregated conclusion." This aggregation can still count a failed or cancelled run, even if a sibling run on the exact same head SHA was successful. This often occurs when workflows are configured with concurrency groups and cancel-in-progress: true, leading to overlapping pull_request events (e.g., opened followed quickly by labeled or synchronize) where an earlier run is cancelled, leaving its failed status to linger in the merge gate's evaluation.
Reproducing the Blocked State
Community member DankerMu meticulously documented several instances and even performed controlled experiments to reproduce this state. Key observations included:
- Multiple check suites appearing for the same PR head, triggered by different
pull_requestevents (e.g.,openedvs.labeled). - One suite successfully passing all required checks, while another, often newer and cancelled, reported failure.
gh pr checks --requiredshowing PASS, butmergeStateStatusremaining BLOCKED.- Attempts to merge via
gh pr mergebeing rejected by branch policy.
The critical insight was that the merge gate seemed to be evaluating the failed aggregate from a cancelled suite, even when a successful suite existed for the same commit.
Strategies for Unblocking Your PRs Without New Commits
Fortunately, the community discussion also yielded reliable methods to resolve this state without resorting to adding an empty commit or weakening branch protection rules. These methods force GitHub to re-evaluate the check suite's aggregated conclusion:
1. Rerun the Affected Failed/Cancelled Suite
This is the most direct and proven method. Instead of rerunning the already-green suite, identify the specific workflow run that was cancelled or failed (even if it's an older attempt) and rerun all jobs within that suite. This creates a new attempt for that suite, and if successful, will update the aggregated conclusion that the merge gate uses.
gh run rerun [RUN_ID] --all-jobsAfter this, the mergeStateStatus should transition to CLEAN, allowing for a normal merge:
gh pr merge [PR_NUMBER] --merge --match-head-commit [COMMIT_SHA] --delete-branch2. Close and Reopen the Pull Request
This is often the "cheapest" lever. Closing and then immediately reopening the PR triggers a pull_request event with the action reopened. If your required-check workflows listen for this event (which is default unless explicitly narrowed), they will re-run fresh, leading to a new check suite evaluation and potentially resolving the block.
3. Add a New Commit to the Head (Last Resort)
While effective, this is generally less desirable as it changes the head SHA. Any new commit, even a trivial one, will trigger new check runs and create new check suites, effectively bypassing the problematic state of the old ones. This is the fallback if the previous two methods are not feasible (e.g., workflow not configured for reopened, or the specific failed run record is unavailable).
Conclusion
Navigating the intricacies of a powerful software engineering tool like GitHub requires a deep understanding of its internal mechanisms. This particular issue highlights a subtle but significant difference in how check statuses are presented versus how they are enforced for merging. By understanding the "state split" and knowing how to re-aggregate check suite conclusions, developers can efficiently troubleshoot and resolve these frustrating PR merge blocks, ensuring a smoother CI/CD pipeline and enhanced developer productivity.
