prReadyForReview (src/queue/processors.ts) defers review forever for a PR with a missing
required CI context, even when the PR already has an independently terminal, CI-agnostic signal:
a dirty (conflicting) base.
Root cause
In the ci?.hasPending branch (~processors.ts:3550-3651), the staleness cap is computed correctly:
const deferCapMs = ci.hasMissingRequiredContext ? MISSING_REQUIRED_CONTEXT_DEFER_MS : STUCK_CI_DEFER_MS;
But the ci.hasMissingRequiredContext branch right below it ignores that cap entirely and always
returns false (defer):
if (ci.hasMissingRequiredContext) {
await recordAuditEvent(env, {
...
detail: "Required CI context is still missing — review deferred instead of publishing a passing gate before expected CI reports",
...
}).catch(() => undefined);
return false;
}
Its sibling "generic stuck CI" branch immediately below (~3585-3649) has a proper
finalize-past-cap + per-head-SHA once-only guard (CI_STUCK_FINALIZE_GUARD_EVENT_TYPE) so a
permanently-stuck PR still eventually surfaces. The hasMissingRequiredContext branch has no such
escalation — it defers unconditionally, every pass, forever.
For most missing-required-context PRs that's the deliberate, correct behavior (see the two existing
tests keeps deferring a missing-required-context PR ... (#selfhost-ci-deferral-staleness) in
test/unit/queue.test.ts) — we must not publish a possibly-premature passing gate before an
expected required check reports. But it's wrong for a PR whose base is already dirty: the
disposition planner (src/settings/agent-actions.ts) computes
isConflict = input.pr.mergeableState === "dirty" and lets that close a contributor PR
regardless of CI state (willClose = ... || isConflict, and pendingCiMayStillCloseForTerminalReason
explicitly allows a terminal conflict close to proceed even while CI is still pending). A dirty-base
PR can never acquire the missing required context by waiting, so today it just sits open,
unlabeled, with no gate check-run, indefinitely.
Observed live: #7537 — mergeable: CONFLICTING, mergeStateStatus: DIRTY,
no labels, no LoopOver gate check-run. audit_events shows the same
"Required CI context is still missing — review deferred..." detail repeating every ~4 minutes for
40+ minutes with no resolution, matching this code path exactly.
Fix: in the hasMissingRequiredContext branch, skip the indefinite defer when the PR's live
mergeable state is already dirty — fall through to the existing finalize-past-cap + per-SHA-guard
logic so the pipeline reaches the disposition planner, which already knows how to close it. Every
other missing-required-context PR keeps deferring exactly as before (regression-tested).
prReadyForReview(src/queue/processors.ts) defers review forever for a PR with a missingrequired CI context, even when the PR already has an independently terminal, CI-agnostic signal:
a dirty (conflicting) base.
Root cause
In the
ci?.hasPendingbranch (~processors.ts:3550-3651), the staleness cap is computed correctly:But the
ci.hasMissingRequiredContextbranch right below it ignores that cap entirely and alwaysreturns
false(defer):Its sibling "generic stuck CI" branch immediately below (~3585-3649) has a proper
finalize-past-cap + per-head-SHA once-only guard (
CI_STUCK_FINALIZE_GUARD_EVENT_TYPE) so apermanently-stuck PR still eventually surfaces. The
hasMissingRequiredContextbranch has no suchescalation — it defers unconditionally, every pass, forever.
For most missing-required-context PRs that's the deliberate, correct behavior (see the two existing
tests
keeps deferring a missing-required-context PR ... (#selfhost-ci-deferral-staleness)intest/unit/queue.test.ts) — we must not publish a possibly-premature passing gate before anexpected required check reports. But it's wrong for a PR whose base is already dirty: the
disposition planner (
src/settings/agent-actions.ts) computesisConflict = input.pr.mergeableState === "dirty"and lets that close a contributor PRregardless of CI state (
willClose = ... || isConflict, andpendingCiMayStillCloseForTerminalReasonexplicitly allows a terminal conflict close to proceed even while CI is still pending). A dirty-base
PR can never acquire the missing required context by waiting, so today it just sits open,
unlabeled, with no gate check-run, indefinitely.
Observed live: #7537 —
mergeable: CONFLICTING,mergeStateStatus: DIRTY,no labels, no LoopOver gate check-run.
audit_eventsshows the same"Required CI context is still missing — review deferred..."detail repeating every ~4 minutes for40+ minutes with no resolution, matching this code path exactly.
Fix: in the
hasMissingRequiredContextbranch, skip the indefinite defer when the PR's livemergeable state is already
dirty— fall through to the existing finalize-past-cap + per-SHA-guardlogic so the pipeline reaches the disposition planner, which already knows how to close it. Every
other missing-required-context PR keeps deferring exactly as before (regression-tested).