diff --git a/src/github/backfill.ts b/src/github/backfill.ts index 4e9eab7305..3d8d8804ea 100644 --- a/src/github/backfill.ts +++ b/src/github/backfill.ts @@ -58,7 +58,7 @@ import type { RepositorySettings, } from "../types"; import { errorMessage, nowIso, repoParts, strippedErrorMessage } from "../utils/json"; -import { createInstallationToken, getAppInstallation, GITTENSORY_GATE_CHECK_NAME } from "./app"; +import { createInstallationToken, getAppInstallation, GITTENSORY_CONTEXT_CHECK_NAME, GITTENSORY_GATE_CHECK_NAME } from "./app"; type GitHubLabelPayload = { name: string; @@ -1907,6 +1907,12 @@ async function fetchPullRequestChecks( const CI_FAILING_CONCLUSIONS = new Set(["failure", "timed_out", "cancelled", "action_required", "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 +// the very review they're blocking runs → the PR defers forever). Excluded from the CI aggregate entirely. +// (#gate-self-deadlock — froze green-CI PRs as "CI still running". The Gate alone wasn't enough: the Context +// check is posted the same way and re-created the deadlock, so exclude ALL bot-owned checks.) +const BOT_OWNED_CHECK_NAMES = new Set([GITTENSORY_GATE_CHECK_NAME, GITTENSORY_CONTEXT_CHECK_NAME]); export type LiveCiAggregate = { ciState: "passed" | "failed" | "pending" | "unverified"; @@ -1985,11 +1991,7 @@ export async function fetchLiveCiAggregate( ).catch(() => undefined); if (!result) break; for (const run of result.data.check_runs ?? []) { - // The bot's OWN gate check is NOT "CI" to wait on. It posts the gate as in_progress and then concludes it - // AFTER reviewing — so counting it here self-deadlocks: the review waits for all CI to finish, the gate - // never finishes (it's pending until the review it is blocking runs), and the PR defers forever. Skip it. - // (#gate-self-deadlock — this was deferring green-CI PRs as "CI still running" indefinitely.) - if (run.name === GITTENSORY_GATE_CHECK_NAME) continue; + if (BOT_OWNED_CHECK_NAMES.has(run.name)) continue; // never wait on the bot's own Gate/Context checks (see above) total += 1; const conclusion = (run.conclusion ?? "").toLowerCase(); const status = (run.status ?? "").toLowerCase(); @@ -2017,7 +2019,7 @@ export async function fetchLiveCiAggregate( ).catch(() => undefined); for (const ctx of statusResult?.data.statuses ?? []) { const name = ctx.context ?? "status"; - if (name === GITTENSORY_GATE_CHECK_NAME) continue; // never wait on the bot's own gate (see #gate-self-deadlock above) + if (BOT_OWNED_CHECK_NAMES.has(name)) continue; // never wait on the bot's own Gate/Context checks (see #gate-self-deadlock above) total += 1; const state = (ctx.state ?? "").toLowerCase(); if (state === "failure" || state === "error") { diff --git a/test/unit/backfill.test.ts b/test/unit/backfill.test.ts index 6e04aed959..5bb008c540 100644 --- a/test/unit/backfill.test.ts +++ b/test/unit/backfill.test.ts @@ -2685,7 +2685,7 @@ describe("GitHub backfill", () => { expect(aggregate.nonRequiredFailingDetails).toEqual([]); }); - it("ignores the bot's OWN Gittensory Gate check so it never self-deadlocks (#gate-self-deadlock)", async () => { + it("ignores ALL of the bot's OWN checks (Gate + Context) so it never self-deadlocks (#gate-self-deadlock)", async () => { const env = createTestEnv({ GITHUB_PUBLIC_TOKEN: "public-token" }); vi.stubGlobal("fetch", async (input: RequestInfo | URL) => { const url = input.toString(); @@ -2693,9 +2693,10 @@ describe("GitHub backfill", () => { return Response.json({ check_runs: [ { name: "test", status: "completed", conclusion: "success" }, - // the bot's OWN gate, still in_progress (posted but not yet concluded). Counting it would defer - // the very review that concludes it — the self-deadlock that froze green-CI PRs as "CI pending". + // BOTH bot-posted checks, still in_progress (posted but not yet concluded). Counting EITHER would + // defer the very review that concludes it — the self-deadlock that froze green-CI PRs as "CI pending". { name: "Gittensory Gate", status: "in_progress", conclusion: null }, + { name: "Gittensory Context", status: "in_progress", conclusion: null }, ], }); } @@ -2703,10 +2704,10 @@ describe("GitHub backfill", () => { return new Response("not found", { status: 404 }); }); - // The gate is among the branch-protection required contexts, yet it MUST be excluded from the CI wait. - const aggregate = await fetchLiveCiAggregate(env, "JSONbored/metagraphed", "headsha", "public-token", new Set(["test", "Gittensory Gate"])); + // Both bot checks are excluded from the CI wait even if listed among the required contexts. + const aggregate = await fetchLiveCiAggregate(env, "JSONbored/metagraphed", "headsha", "public-token", new Set(["test", "Gittensory Gate", "Gittensory Context"])); - expect(aggregate.ciState).toBe("passed"); // would be "pending" if the in_progress gate were counted + expect(aggregate.ciState).toBe("passed"); // would be "pending" if either in_progress bot check were counted expect(aggregate.failingDetails).toEqual([]); }); });