From 81acfb0fa53c72121f81b3214f5ee352bd7adf90 Mon Sep 17 00:00:00 2001 From: ***** <721466+soodoku@users.noreply.github.com> Date: Sat, 8 Aug 2026 23:22:43 -0700 Subject: [PATCH] Name the required check that never ran A green rollup is not a satisfied ruleset. On appeler/pranaam#10 all seven reported checks passed while the required 'build' context never ran at all -- its workflow had been cancelled by a concurrency collision -- and the PR sat BLOCKED for weeks looking entirely green. Counting reported checks cannot see that. The sweep already refused to act on BLOCKED, so nothing unsafe happened, but it reported only the symptom. It now fetches the ruleset's required contexts and names the ones with no check run on the PR. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/dependabot-auto-merge.yml | 34 ++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 637e804..8e241d5 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -151,6 +151,21 @@ jobs: > prs.json python3 -c "import json;print('collected',len(json.load(open('prs.json'))),'open Dependabot PRs')" + # Required contexts, so a check that never reported can be named. A + # green rollup is not the same as a satisfied ruleset: on + # appeler/pranaam#10 all seven reported checks passed while the + # required `build` context never ran at all -- its workflow had been + # cancelled by a concurrency collision -- and the PR sat BLOCKED for + # weeks looking entirely green. Counting reported checks cannot see + # that; comparing against the requirement can. + gh api "repos/${GH_REPO}/rulesets" --jq '.[].id' 2>/dev/null \ + | while read -r id; do + gh api "repos/${GH_REPO}/rulesets/${id}" --jq \ + '.rules[]? | select(.type=="required_status_checks") + | .parameters.required_status_checks[].context' 2>/dev/null + done | sort -u > required.txt || true + echo "required contexts: $(tr '\n' ' ' < required.txt)" + # Decide per PR, print one line for every one of them, and act. Check # state is read from statusCheckRollup rather than from mergeStateStatus # alone: CLEAN is GitHub's opinion about mergeability, and this job needs @@ -185,6 +200,17 @@ jobs: return "running" return "green" if all(s in TERMINAL_OK for s in states) else "failing" + def never_reported(pr): + """Required contexts with no check run at all on this PR.""" + seen = {c.get("name") or c.get("context") for c in + (pr.get("statusCheckRollup") or [])} + return sorted(required - seen) + + try: + required = {ln.strip() for ln in open("required.txt") if ln.strip()} + except OSError: + required = set() + for pr in json.load(open("prs.json")): n = pr["number"] names = {l["name"] for l in pr.get("labels") or []} @@ -194,7 +220,13 @@ jobs: elif pr.get("autoMergeRequest"): verdict, act = "already armed", "none" elif pr["mergeStateStatus"] in {"DIRTY", "BLOCKED", "DRAFT"}: - verdict, act = f"not mergeable ({pr['mergeStateStatus']})", "none" + # Name the missing requirement rather than only its symptom: + # "BLOCKED" sends a reader looking for a failing check that + # does not exist. + absent = never_reported(pr) + reason = (f"required never ran: {','.join(absent)}" if absent + else pr["mergeStateStatus"]) + verdict, act = f"not mergeable ({reason})", "none" else: state = check_state(pr) verdict, act = {