From acaf54af5dc90f064e7567681feaf0752808203b Mon Sep 17 00:00:00 2001 From: Bibek Chaudhary Date: Mon, 13 Jul 2026 13:10:56 +0545 Subject: [PATCH] gated_merge: don't merge without a green signal; --auto on branch-policy block Two fixes for the failure seen on RealRate-Private#2220, where the log showed: "No CI checks registered within 180s; merging on the existing signal." "... is not mergeable: the base branch policy prohibits the merge. ... add the `--auto` flag." Root cause: under runner-queue contention (a batch of Dependabot PRs), the target repo's CI checks had not *registered* within the 180s appearance grace, so the script fell through to "merge on the existing signal" and attempted an immediate merge before any CI existed -- which the base-branch ruleset correctly rejected (its required workflow/checks were not yet satisfied). Fix 1 -- never merge blind: if no checks register within the grace window, route to a human instead of merging. Skipping the gate exactly when checks are merely delayed defeats its purpose. Also raise the grace 180s -> 300s to absorb queue delays. Fix 2 -- respect the branch policy: do_merge now tries an immediate merge and, if the ruleset prohibits it ("add the --auto flag"), enables --auto so GitHub completes the merge once requirements are met. Our checks are already green at that point, so --auto defers to the branch policy without skipping CI. Any other rejection still routes to a human, now with the actual gh error text. shellcheck clean; regex verified against the real error message. --- .github/review-tooling/gated_merge.sh | 37 ++++++++++++++++++++------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/.github/review-tooling/gated_merge.sh b/.github/review-tooling/gated_merge.sh index 7de1459..a6b702e 100755 --- a/.github/review-tooling/gated_merge.sh +++ b/.github/review-tooling/gated_merge.sh @@ -25,8 +25,9 @@ # GITHUB_RUN_ID (auto on runners) this run's id; excludes our own check # CI_GATE_ENABLED default "true"; "false" = kill switch, native --auto merge # MERGE_METHOD default "rebase" -# GATE_APPEAR_SECONDS default 180; grace for checks to register before we treat -# the repo as having no gating CI +# GATE_APPEAR_SECONDS default 300; how long to wait for checks to register +# before giving up and routing to a human (never merges +# without a green signal, even if the repo has no CI) # GATE_TIMEOUT_SECONDS default 1800; overall budget to wait for checks to finish # GATE_POLL_SECONDS default 30; poll interval set -uo pipefail @@ -35,7 +36,7 @@ set -uo pipefail CI_GATE_ENABLED="${CI_GATE_ENABLED:-true}" MERGE_METHOD="${MERGE_METHOD:-rebase}" -APPEAR="${GATE_APPEAR_SECONDS:-180}" +APPEAR="${GATE_APPEAR_SECONDS:-300}" TIMEOUT="${GATE_TIMEOUT_SECONDS:-1800}" POLL="${GATE_POLL_SECONDS:-30}" @@ -57,8 +58,24 @@ route_to_human() { } do_merge() { - gh pr merge --"$MERGE_METHOD" "$PR_URL" \ - || route_to_human "all checks passed but the merge was rejected (branch protection, conflict, or a required human review)." + # Try an immediate merge first. The base branch's ruleset can prohibit an + # immediate merge until its required workflow/checks settle ("the base branch + # policy prohibits the merge ... add the --auto flag"); in that case enable + # auto-merge so GitHub completes the merge once the policy is satisfied. Our + # own checks are already green here, so --auto only defers to the branch policy, + # it does not skip CI. Anything else -> route to a human. + local err + if err="$(gh pr merge --"$MERGE_METHOD" "$PR_URL" 2>&1)"; then + echo "Merged." + return 0 + fi + if printf '%s' "$err" | grep -qiE "add the .--auto. flag|base branch policy prohibits"; then + if gh pr merge --auto --"$MERGE_METHOD" "$PR_URL"; then + echo "Immediate merge blocked by branch policy; enabled --auto (GitHub will complete it once requirements are met)." + return 0 + fi + fi + route_to_human "the merge was rejected: $(printf '%s' "$err" | tail -1)" } # The PR's checks minus THIS workflow's own run, matched by run id inside the @@ -75,14 +92,16 @@ other_checks() { start="$SECONDS" -# 1) Wait for CI to register. A repo with no gating CI merges on the existing -# signal (patch/minor policy, or the AI verdict for majors) -- unchanged. +# 1) Wait for CI to register. Checks can be slow to appear under runner-queue +# contention, so we wait up to APPEAR. We do NOT merge on "no checks" -- that +# would skip the gate exactly when checks are merely delayed (and an immediate +# merge before the base-branch policy's requirements exist gets rejected +# anyway). If nothing registers, route to a human rather than merge blind. while :; do checks="$(other_checks)" [ "$(echo "$checks" | jq 'length')" -gt 0 ] && break if [ $((SECONDS - start)) -ge "$APPEAR" ]; then - echo "No CI checks registered within ${APPEAR}s; merging on the existing signal." - do_merge + route_to_human "no CI checks registered within ${APPEAR}s (checks delayed, or the repo has none) -- not auto-merging without a green signal." exit 0 fi sleep "$POLL"