Problem
The backlog-vs-fresh fairness lane — the mechanism that exists to stop fresh webhooks starving
backlog-convergence work — is inert whenever any unclassified priority-10 webhook is due, which on a repo
with CI is essentially always.
Before either lane-scoped claim, the backend computes maxDueUnclassifiedForegroundPriority() and uses
candidate.priority > $2 (strictly greater) with lanePriorityFloor = unclassifiedPriority
(src/selfhost/pg-queue.ts:1017-1020, :1050-1057).
foregroundLaneForJob (src/selfhost/queue-fairness.ts:12, :27-47) classifies only
pull_request opened|reopened|synchronize|ready_for_review as fresh, and backlog-convergence:-prefixed
agent-regate-pr as backlog. Everything else is lane null. But githubWebhookPriority returns 10
for every other webhook — check_suite.completed, check_run.completed, issue_comment,
pull_request.closed/labeled, review events.
Backlog rows are priority 9 and fresh rows are priority 10. Neither can satisfy > 10. So the moment one
unclassified priority-10 row is due, both lane claims return nothing and the claim falls back to plain
priority DESC ordering — exactly the starvation queue-fairness.ts was written to fix, and its header
says so:
a github-webhook PR-refresh row (priority 10) ALWAYS wins over an agent-regate-pr
backlog-convergence row (priority 9)
Trigger
Any repo with CI. #9054 records 4,900 stuck check_suite.completed deliveries in this deployment — that
event class alone keeps the unclassified due-set non-empty on a busy fleet.
src/selfhost/backlog-convergence.ts:16-18 explicitly relies on the opposite ("this sweeper's own
claim-time priority … means its output already competes ahead of fresh webhook work").
Impact
Backlog-convergence PRs — open PRs whose public review surface was never published for their current head
— can sit unserved indefinitely. There is no aging or priority boost anywhere in the fairness mechanism:
unlike the maintenance lane, it has no trickle escape.
Requirements
- Give the lane claim a bounded age escape, mirroring
trickle_max_defer_age.
- Or compare against the unclassified row's
claim_sort_key/age rather than its raw priority, so an old
backlog row can outrank a fresh unclassified one.
- Either way, add an invariant test that a backlog row cannot be starved past a bounded age by a
continuous stream of unclassified priority-10 webhooks.
Test Coverage Requirements
99%+ patch coverage, branch-counted; both arms of the escape condition.
Links & Resources
maintainer-only — scheduler fairness.
Problem
The backlog-vs-fresh fairness lane — the mechanism that exists to stop fresh webhooks starving
backlog-convergence work — is inert whenever any unclassified priority-10 webhook is due, which on a repo
with CI is essentially always.
Before either lane-scoped claim, the backend computes
maxDueUnclassifiedForegroundPriority()and usescandidate.priority > $2(strictly greater) withlanePriorityFloor = unclassifiedPriority(
src/selfhost/pg-queue.ts:1017-1020,:1050-1057).foregroundLaneForJob(src/selfhost/queue-fairness.ts:12,:27-47) classifies onlypull_requestopened|reopened|synchronize|ready_for_reviewasfresh, andbacklog-convergence:-prefixedagent-regate-prasbacklog. Everything else is lanenull. ButgithubWebhookPriorityreturns 10for every other webhook —
check_suite.completed,check_run.completed,issue_comment,pull_request.closed/labeled, review events.Backlog rows are priority 9 and fresh rows are priority 10. Neither can satisfy
> 10. So the moment oneunclassified priority-10 row is due, both lane claims return nothing and the claim falls back to plain
priority DESCordering — exactly the starvationqueue-fairness.tswas written to fix, and its headersays so:
Trigger
Any repo with CI. #9054 records 4,900 stuck
check_suite.completeddeliveries in this deployment — thatevent class alone keeps the unclassified due-set non-empty on a busy fleet.
src/selfhost/backlog-convergence.ts:16-18explicitly relies on the opposite ("this sweeper's ownclaim-time priority … means its output already competes ahead of fresh webhook work").
Impact
Backlog-convergence PRs — open PRs whose public review surface was never published for their current head
— can sit unserved indefinitely. There is no aging or priority boost anywhere in the fairness mechanism:
unlike the maintenance lane, it has no trickle escape.
Requirements
trickle_max_defer_age.claim_sort_key/age rather than its raw priority, so an oldbacklog row can outrank a fresh unclassified one.
continuous stream of unclassified priority-10 webhooks.
Test Coverage Requirements
99%+ patch coverage, branch-counted; both arms of the escape condition.
Links & Resources
src/selfhost/pg-queue.ts~1017-1020, ~1050-1057;src/selfhost/queue-fairness.ts~12, ~27-47;src/selfhost/queue-common.ts~611-640;src/selfhost/backlog-convergence.ts~16-18check_suite.completedbacklog that keeps the unclassified set non-empty)maintainer-only — scheduler fairness.