Bug
The pr-auto-review ready-check (pr-auto-review-reusable.yml, check-and-dispatch job) gates dispatch on every check passing:
CHECKS=$(gh pr checks "$PR_URL" --json bucket,name ...)
# map(select(.name != $self)) | map(select(.bucket != "pass" and .bucket != "skipping")) | length
This counts non-required and cancelled check contexts as "not passing", so it refuses to dispatch the review agent on PRs that are actually mergeable.
Repro (observed on #678)
dev-lead's review-changes commits carry [skip ci-relay], and dev-lead's per-PR concurrency cancels superseded runs. Each leaves stale dev-lead / ci-relay + dev-lead / dispatch check runs in state CANCELLED. These contexts are not in the branch ruleset's required set (required = SonarCloud, CodeQL, AgentShield, Detect ecosystems, Lint, ShellCheck, Agent Security Scan). Yet the ready-check reported 4 of 24 check(s) are not yet passing — skipping and never dispatched, even though the PR was mergeable: MERGEABLE with all required checks green, review-approved-ready, zero unresolved threads.
Impact: the auto-approval path silently fails for exactly the PRs that go through a review round-trip (multiple dev-lead runs → concurrency cancellations). Had to fall back to manually dispatching the review agent + a normal merge.
Fix options (pick one, prefer A)
A. Only count REQUIRED checks. Fetch the branch's required contexts (ruleset rules/branches/<branch> → required_status_checks) and evaluate the passing-gate against that set, not all contexts. Most correct — mirrors what actually blocks merge.
B. Treat cancel (and skipping, already handled) as non-blocking. Add .bucket != "cancel" to the filter. Simpler, but still counts non-required failing checks. A cancelled advisory run is not a signal.
C. Use mergeStateStatus. Gate on BLOCKED vs CLEAN/UNSTABLE from the GraphQL merge state — UNSTABLE (non-required failing) is mergeable. Leans on GitHub's own required-vs-not determination.
Recommend A (explicit, testable) with B as a cheap immediate mitigation. Whatever the choice, extract the passing-gate into a pure helper + bats (the house pattern — Layer 0 of #668) so the decision matrix is pinned.
Acceptance
- A PR with all required checks green + approved-ready + no unresolved threads dispatches the review agent even when a non-required or cancelled advisory context (e.g.
dev-lead / ci-relay) is present.
- Required-check failure/pending still blocks (unchanged).
- Decision logic unit-tested.
Bug
The pr-auto-review ready-check (
pr-auto-review-reusable.yml,check-and-dispatchjob) gates dispatch on every check passing:This counts non-required and cancelled check contexts as "not passing", so it refuses to dispatch the review agent on PRs that are actually mergeable.
Repro (observed on #678)
dev-lead's review-changes commits carry
[skip ci-relay], and dev-lead's per-PR concurrency cancels superseded runs. Each leaves staledev-lead / ci-relay+dev-lead / dispatchcheck runs in state CANCELLED. These contexts are not in the branch ruleset's required set (required = SonarCloud, CodeQL, AgentShield, Detect ecosystems, Lint, ShellCheck, Agent Security Scan). Yet the ready-check reported4 of 24 check(s) are not yet passing — skippingand never dispatched, even though the PR wasmergeable: MERGEABLEwith all required checks green, review-approved-ready, zero unresolved threads.Impact: the auto-approval path silently fails for exactly the PRs that go through a review round-trip (multiple dev-lead runs → concurrency cancellations). Had to fall back to manually dispatching the review agent + a normal merge.
Fix options (pick one, prefer A)
A. Only count REQUIRED checks. Fetch the branch's required contexts (ruleset
rules/branches/<branch>→required_status_checks) and evaluate the passing-gate against that set, not all contexts. Most correct — mirrors what actually blocks merge.B. Treat
cancel(andskipping, already handled) as non-blocking. Add.bucket != "cancel"to the filter. Simpler, but still counts non-required failing checks. A cancelled advisory run is not a signal.C. Use
mergeStateStatus. Gate onBLOCKEDvsCLEAN/UNSTABLEfrom the GraphQL merge state —UNSTABLE(non-required failing) is mergeable. Leans on GitHub's own required-vs-not determination.Recommend A (explicit, testable) with B as a cheap immediate mitigation. Whatever the choice, extract the passing-gate into a pure helper + bats (the house pattern — Layer 0 of #668) so the decision matrix is pinned.
Acceptance
dev-lead / ci-relay) is present.