From 63625c528b0f1a1636cf85a28d6422575feb7e16 Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Tue, 23 Jun 2026 19:19:11 -0700 Subject: [PATCH] fix(ci): treat fork 'action_required' as pending, not failed (stops the residual false-close) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The harm-stop (#1138) held ciState='unverified' fork PRs but MISSED this path: a fork awaiting maintainer 'Approve and run' surfaces its required checks with conclusion='action_required', which CI_FAILING_CONCLUSIONS folded into failingDetails → ciState='failed' → the agent still one-shot CLOSED the fork citing 'CI is failing', even though no check ever ran. Removing 'action_required' from the failing set lets it fall through to anyPending → ciState='pending' → the PR is DEFERRED/held, never closed, until its runs are approved (manually or by upcoming fork CI auto-approval). Full suite green (3608). --- src/github/backfill.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/github/backfill.ts b/src/github/backfill.ts index 2e04c90904..bf6dfd63de 100644 --- a/src/github/backfill.ts +++ b/src/github/backfill.ts @@ -1907,7 +1907,12 @@ async function fetchPullRequestChecks( return { check_runs: checkRuns }; } -const CI_FAILING_CONCLUSIONS = new Set(["failure", "timed_out", "cancelled", "action_required", "startup_failure"]); +// NOTE: "action_required" is deliberately NOT here. A fork PR awaiting maintainer "Approve and run" surfaces its +// required checks with conclusion="action_required" — that is NOT a failure, it is awaiting-approval. Treating it +// as failing made ciState="failed" → the agent one-shot CLOSED the fork ("CI is failing") even though no check +// ever ran. Excluded here, an action_required check falls through to anyPending → ciState="pending" → the PR is +// DEFERRED/held (never closed) until its runs are approved (manually, or auto-approved by fork CI auto-approval). (#fork-action-required) +const CI_FAILING_CONCLUSIONS = new Set(["failure", "timed_out", "cancelled", "startup_failure"]); const CI_PASSING_CONCLUSIONS = new Set(["success", "neutral", "skipped"]); // The bot's OWN check-runs — it posts these (in_progress, then concluded) as PART OF reviewing. They are NOT // "CI to wait on": counting them self-deadlocks (the review waits for all CI to finish; these only finish when