Problem
selectBacklogConvergenceCandidates (src/selfhost/backlog-convergence.ts:42-56) filters, sorts by
createdAt ascending, then .slice(0, 5). Only after that does the caller apply the exhaustion
filter and bail (src/queue/processors.ts:1820-1828):
const allCandidates = selectBacklogConvergenceCandidates({ pulls: openPullRequests }); // already sliced to 5
const exhaustedFlags = await Promise.all(allCandidates.map((pr) => isRegateRepairExhausted(env, repoFullName, pr)));
const candidates = allCandidates.filter((_pr, index) => !exhaustedFlags[index]);
if (candidates.length === 0) return;
The sort key is PR creation time, which never changes, and the selection predicate
(lastPublishedSurfaceSha !== headSha) only clears on a genuinely successful publish. So five old,
permanently-unpublishable PRs permanently occupy all five slots.
Trigger
Five old open PRs whose surface publish keeps failing — a lost checks:write, the duplicate-check-run
race in #9013, or a repo-level publish error. Each burns its 5 attempts per SHA, candidates becomes
empty, the sweep returns early, and the 6th…Nth PR needing convergence is never even considered.
REGATE_REPAIR_ATTEMPT_LOOKBACK_MS is 24h, so this repeats on a 24-hour cycle rather than resolving.
Impact
The durable catch-all for "this PR has no published review surface" silently stops covering every PR
behind the five oldest — precisely the stranding class it exists to repair. Nothing surfaces the fact
that the sweep is returning early every cycle.
Requirements
- Move the
isRegateRepairExhausted filter inside or ahead of the slice, so the cap selects 5
actionable candidates.
- Emit a metric/log when the sweep exits with an all-exhausted candidate set, so a permanently wedged head
of the list is visible rather than silent.
- Consider a secondary sort or rotation so a persistently failing PR cannot hold a slot forever even
after the filter fix.
Test Coverage Requirements
99%+ patch coverage, branch-counted; a regression test with 5 exhausted + 1 actionable PR asserting the
actionable one is selected.
Links & Resources
maintainer-only — convergence sweep correctness.
Problem
selectBacklogConvergenceCandidates(src/selfhost/backlog-convergence.ts:42-56) filters, sorts bycreatedAtascending, then.slice(0, 5). Only after that does the caller apply the exhaustionfilter and bail (
src/queue/processors.ts:1820-1828):The sort key is PR creation time, which never changes, and the selection predicate
(
lastPublishedSurfaceSha !== headSha) only clears on a genuinely successful publish. So five old,permanently-unpublishable PRs permanently occupy all five slots.
Trigger
Five old open PRs whose surface publish keeps failing — a lost
checks:write, the duplicate-check-runrace in #9013, or a repo-level publish error. Each burns its 5 attempts per SHA,
candidatesbecomesempty, the sweep returns early, and the 6th…Nth PR needing convergence is never even considered.
REGATE_REPAIR_ATTEMPT_LOOKBACK_MSis 24h, so this repeats on a 24-hour cycle rather than resolving.Impact
The durable catch-all for "this PR has no published review surface" silently stops covering every PR
behind the five oldest — precisely the stranding class it exists to repair. Nothing surfaces the fact
that the sweep is returning early every cycle.
Requirements
isRegateRepairExhaustedfilter inside or ahead of theslice, so the cap selects 5actionable candidates.
of the list is visible rather than silent.
after the filter fix.
Test Coverage Requirements
99%+ patch coverage, branch-counted; a regression test with 5 exhausted + 1 actionable PR asserting the
actionable one is selected.
Links & Resources
src/selfhost/backlog-convergence.ts~42-56;src/queue/processors.ts~1820-1828maintainer-only — convergence sweep correctness.