Skip to content

Commit 3c990da

Browse files
soodokuclaude
andauthored
Name the required check that never ran (#19)
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) <noreply@anthropic.com>
1 parent b0c6ff5 commit 3c990da

1 file changed

Lines changed: 33 additions & 1 deletion

File tree

.github/workflows/dependabot-auto-merge.yml

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,21 @@ jobs:
151151
> prs.json
152152
python3 -c "import json;print('collected',len(json.load(open('prs.json'))),'open Dependabot PRs')"
153153
154+
# Required contexts, so a check that never reported can be named. A
155+
# green rollup is not the same as a satisfied ruleset: on
156+
# appeler/pranaam#10 all seven reported checks passed while the
157+
# required `build` context never ran at all -- its workflow had been
158+
# cancelled by a concurrency collision -- and the PR sat BLOCKED for
159+
# weeks looking entirely green. Counting reported checks cannot see
160+
# that; comparing against the requirement can.
161+
gh api "repos/${GH_REPO}/rulesets" --jq '.[].id' 2>/dev/null \
162+
| while read -r id; do
163+
gh api "repos/${GH_REPO}/rulesets/${id}" --jq \
164+
'.rules[]? | select(.type=="required_status_checks")
165+
| .parameters.required_status_checks[].context' 2>/dev/null
166+
done | sort -u > required.txt || true
167+
echo "required contexts: $(tr '\n' ' ' < required.txt)"
168+
154169
# Decide per PR, print one line for every one of them, and act. Check
155170
# state is read from statusCheckRollup rather than from mergeStateStatus
156171
# alone: CLEAN is GitHub's opinion about mergeability, and this job needs
@@ -185,6 +200,17 @@ jobs:
185200
return "running"
186201
return "green" if all(s in TERMINAL_OK for s in states) else "failing"
187202
203+
def never_reported(pr):
204+
"""Required contexts with no check run at all on this PR."""
205+
seen = {c.get("name") or c.get("context") for c in
206+
(pr.get("statusCheckRollup") or [])}
207+
return sorted(required - seen)
208+
209+
try:
210+
required = {ln.strip() for ln in open("required.txt") if ln.strip()}
211+
except OSError:
212+
required = set()
213+
188214
for pr in json.load(open("prs.json")):
189215
n = pr["number"]
190216
names = {l["name"] for l in pr.get("labels") or []}
@@ -194,7 +220,13 @@ jobs:
194220
elif pr.get("autoMergeRequest"):
195221
verdict, act = "already armed", "none"
196222
elif pr["mergeStateStatus"] in {"DIRTY", "BLOCKED", "DRAFT"}:
197-
verdict, act = f"not mergeable ({pr['mergeStateStatus']})", "none"
223+
# Name the missing requirement rather than only its symptom:
224+
# "BLOCKED" sends a reader looking for a failing check that
225+
# does not exist.
226+
absent = never_reported(pr)
227+
reason = (f"required never ran: {','.join(absent)}" if absent
228+
else pr["mergeStateStatus"])
229+
verdict, act = f"not mergeable ({reason})", "none"
198230
else:
199231
state = check_state(pr)
200232
verdict, act = {

0 commit comments

Comments
 (0)