ci(automerge): hold the arm behind a quiet period + unaddressed-findings check - #149
Conversation
…ngs check wxa_vpn#1392: four PRs merged 3m18s-16m17s after a review finding posted, with no commit in response — nothing holds the door between the last review and the merge. The merge is server-side once required checks go green, so the consultation moves to ARM time: wait findings_quiet_minutes (default 20) of review silence, then run the unaddressed-findings detector (vendored twin of dotclaude's bb-unaddressed-findings.sh, fetched from this repo's default branch like classify.mjs); findings decline the arm, disarm any stale arm, and leave a sticky comment. Applies to every arm path, bypass labels included (wxa_vpn#1404 merged 56s after a false Codex P1 — an approval says "I read what was there", not "nothing further may be said"). Never a required check; the manual merge button stays untouched, so a wrong finding costs one click, never a wedge. Detector precision measured 90.9% on a fresh 25-PR corpus (#1369-#1401); 40% of merges carried a real unaddressed finding. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
Risk class: This PR touches one of the blocked path categories from Auto-merge is refused by (This is a policy notice, not a code-quality failure. The classify job itself does not fail — required CI checks remain authoritative for "is the code green.") |
|
Coverage Floor — mode:
|
|
No issues found. Quiet-period + findings gate is well-structured: fail-closed on every error path, timestamp shape validated before use, paginate handling correct in both the workflow step and the vendored detector, and the 18-case selftest covers the critical behavioral branches. |
Implements the chosen option for whois-api-llc/wxa_vpn#1392 ("nothing holds the door between the last review and the merge"): the automerge lane consults the review conversation at ARM time — a quiet period plus the unaddressed-findings detector — instead of arming the instant the gates clear.
Why arm time, not merge time
The reusable never merges; it enables GitHub's native
--auto, and the server merges the moment the last required check goes green. No hook runs at that instant, so the only place a consultation can live is before the arm. Measured gaps on the four incident PRs (wxa_vpn#1385/#1376/#1378/#1345) were 3m18s–16m17s from finding to merge — all inside the 20-minute default window.What the new gate does
Runs only when every existing gate has cleared (i.e. the PR would otherwise arm right now):
findings_quiet_minutes(default 20,0disables) have passed since the newest of: the newest authored (non-merge) commit, the newest inline review comment, and the newest top-level[bot]comment that isn't this workflow's own sticky comment or pr-classify chatter (the gate must not reset its own clock). Sleeping is safe: callers run per-PRcancel-in-progress, so a new push replaces the sleeping run, and the arm still binds--match-head-commit. Chronic chatter is bounded by a 3× cap that declines + disarms..github/scripts/unaddressed-findings.sh(vendored twin of dotclaude'sbb-unaddressed-findings.sh, fetched from this repo's default branch exactly likeclassify.mjs). Findings newer than the last authored commit ⇒ decline the arm, disarm any arm an earlier run placed, and leave a sticky comment with the report.Uniform across every arm path, bypass labels included: wxa_vpn#1404 merged 56 seconds after a (false) Codex P1 because an approval decided minutes earlier raced a review that hadn't finished speaking. The escape for a wrong finding is the one thing this lane never touches — the manual merge click. Detector precision measured 90.9% on a fresh 25-PR corpus (#1369–#1401), and 40% of merges carried a real unaddressed finding, so the decline-to-manual cost is small relative to the caught class.
What it deliberately does not do
always()error-revoke disarm stale arms.risk:blockedmerges stay advisory — both incident P1s were on human click-merges, which only a required check could block, and the detector has no acknowledgment path yet (only a new commit clears a finding). Blocking there is explicitly deferred until an ack path exists; thebb-unaddressed-findings.shpre-merge sweep remains the manual-lane practice.Testing
selftest/test_automerge_findings_gate.sh— 18 cases: 4 structural pins (error-revoke coverage, Enable-condition wiring, interpolation-free extraction, pagination on all three anchor reads) + 9 behavioral cases against stubbedgh/date/sleep(disabled short-circuit, quiet+clean arms, findings decline+disarm+comment, detector-error fails closed, young-activity sleeps the remainder, no-anchor fails closed, own-marker comments don't reset the clock, chatter cap declines+disarms, unreadable commits fail closed) + 2 fixture-driven runs of the vendored detector.test_automerge_hold_gate.sh/base_gate/risk_patterns/riskfile_gateall still pass;actionlintandshellcheck -S warningclean.Rollout notes
findings_quiet_minutes: 0to opt out wholesale.Auto-merge rationale: none — PRs to topcoder1/ci-workflows are always manual-merge (production CI infrastructure).
🤖 Generated with Claude Code