fix(ci): sweep PRs with unknown mergeability — stuck merge-ref is the target pathology (#110945)

Live verification showed dropped-CI PRs report mergeable=null and
mergeable_state=unknown indefinitely: the stuck merge-ref computation is
the same failure that dropped their CI, and close/reopen is what
un-sticks it. The pending-mergeability skip therefore made the primary
repair population permanently unsweepable (three real dropped PRs skipped
across three consecutive sweeps). Keep skipping computed conflicts; a
not-yet-computed conflict costs at most one budgeted re-fire.
This commit is contained in:
Peter Steinberger
2026-07-18 21:37:53 +01:00
committed by GitHub
parent 47f42fdda9
commit 0f5d030cb1
2 changed files with 8 additions and 9 deletions

View File

@@ -37,15 +37,14 @@ export function classifyPrForSweep({ pr, ciRuns, botCloseCount, now }) {
return { action: "skip", reason: "recently-updated" };
}
// A conflicted PR legitimately has no merge ref; CI cannot attach until the
// author resolves, so re-firing would loop forever.
// author resolves, so re-firing would loop forever. Null/unknown mergeability
// is NOT skipped: live testing showed dropped-CI PRs stay mergeable=null
// indefinitely (the stuck merge-ref computation IS the pathology), and
// close/reopen is what un-sticks it. A not-yet-computed conflict costs at
// most one budgeted re-fire before the recomputed false skips it.
if (pr.mergeable === false) {
return { action: "skip", reason: "merge-conflict" };
}
// Pending computation resolves by the next hourly sweep; do not close/reopen
// on an unknown merge state.
if (pr.mergeable === null || pr.mergeable === undefined) {
return { action: "skip", reason: "mergeability-pending" };
}
// Closing a PR silently cancels enabled auto-merge and reopening does not
// restore it; leave those PRs (e.g. generated locale refreshes) to a human.
if (pr.auto_merge) {
@@ -203,7 +202,7 @@ export async function runPrCiSweeper({ github, context, core, dryRun = false, ap
fresh.state !== "open" ||
fresh.head.sha !== pr.head.sha ||
fresh.auto_merge ||
fresh.mergeable !== true
fresh.mergeable === false
) {
core.info(`pr-ci-sweeper: #${pr.number} changed during sweep; leaving it alone`);
continue;