diff --git a/src/queue/processors.ts b/src/queue/processors.ts index c603cad9a6..e352a542e2 100644 --- a/src/queue/processors.ts +++ b/src/queue/processors.ts @@ -1332,7 +1332,14 @@ async function refreshOpenPullRequestsForScheduledSweep( requestedBy: "schedule" | "api" | "test", ): Promise { if (requestedBy !== "schedule") return; - if (!repo || !sweepOpenPullRequestSyncCredentialAvailable(env, repo)) return; + // No installation -> no per-PR regate fan-out will ever happen for this repo (the candidate-selection + // gate below only dispatches agent-regate-pr jobs for installed repos), so refreshing its open-PR list + // here only spends the shared GITHUB_PUBLIC_TOKEN budget on data nothing in THIS sweep will use. A + // registry-only repo (registry/sync.ts, isRegistered) still gets its own data kept fresh by the + // dedicated backfill-registered-repos/refresh-registry jobs, so skipping here is pure waste removal, + // not a functionality gap (#audit-rate-headroom, #sweep-uninstalled-budget-waste). + if (!repo?.installationId) return; + if (!sweepOpenPullRequestSyncCredentialAvailable(env, repo)) return; const segment = await getRepoSyncSegment( env, repo.fullName, diff --git a/test/unit/queue.test.ts b/test/unit/queue.test.ts index 798423a3cf..1dca51a38d 100644 --- a/test/unit/queue.test.ts +++ b/test/unit/queue.test.ts @@ -6737,6 +6737,54 @@ describe("queue processors", () => { warn.mockRestore(); }); + it("REGRESSION (#sweep-uninstalled-budget-waste): a scheduled sweep never refreshes open PRs (via the shared GITHUB_PUBLIC_TOKEN) for a registered-but-uninstalled repo, since no per-PR fan-out will ever follow", async () => { + const sent: import("../../src/types").JobMessage[] = []; + const env = createTestEnv({ + GITHUB_PUBLIC_TOKEN: "public-token", + JOBS: { + async send(m: import("../../src/types").JobMessage) { + sent.push(m); + }, + } as unknown as Queue, + }); + // Registered (e.g. via the subnet registry sync) but NOT installed — no installationId. + await upsertRepositoryFromGitHub(env, { name: "no-install", full_name: "owner/no-install", private: false, owner: { login: "owner" } }); + await upsertRepositorySettings(env, { repoFullName: "owner/no-install", autonomy: { merge: "auto" } }); + const segmentSpy = vi.spyOn(repositoriesModule, "getRepoSyncSegment"); + const backfillSpy = vi.spyOn(backfillModule, "backfillRepositorySegment"); + vi.setSystemTime(new Date("2026-05-28T02:00:00.000Z")); + + await processJob(env, { type: "agent-regate-sweep", requestedBy: "schedule", repoFullName: "owner/no-install" }); + + expect(segmentSpy).not.toHaveBeenCalled(); + expect(backfillSpy).not.toHaveBeenCalled(); + expect(sent.filter((job) => job.type === "agent-regate-pr")).toEqual([]); + segmentSpy.mockRestore(); + backfillSpy.mockRestore(); + }); + + it("scheduled sweeps DO still refresh open PRs for an installed repo even when GITHUB_PUBLIC_TOKEN is also configured (installation presence gates the skip, not credential kind)", async () => { + const sent: import("../../src/types").JobMessage[] = []; + const env = createTestEnv({ + GITHUB_PUBLIC_TOKEN: "public-token", + JOBS: { + async send(m: import("../../src/types").JobMessage) { + sent.push(m); + }, + } as unknown as Queue, + }); + await upsertInstallation(env, { action: "created", installation: { id: 9405, account: { login: "owner", id: 1, type: "Organization" }, target_type: "Organization", repository_selection: "selected", permissions: {}, events: [] } }); + await upsertRepositoryFromGitHub(env, { name: "agent-repo", full_name: "owner/agent-repo", private: false, owner: { login: "owner" } }, 9405); + await upsertRepositorySettings(env, { repoFullName: "owner/agent-repo", autonomy: { merge: "auto" }, gateCheckMode: "off", checkRunMode: "off", commentMode: "off", publicSurface: "off" }); + const backfillSpy = vi.spyOn(backfillModule, "backfillRepositorySegment").mockResolvedValueOnce(undefined as never); + vi.setSystemTime(new Date("2026-05-28T02:00:00.000Z")); + + await processJob(env, { type: "agent-regate-sweep", requestedBy: "schedule", repoFullName: "owner/agent-repo" }); + + expect(backfillSpy).toHaveBeenCalledWith(env, expect.objectContaining({ segment: "open_pull_requests", mode: "light", force: true })); + backfillSpy.mockRestore(); + }); + it("scheduled sweeps refresh incomplete open-PR sync segments", async () => { const sent: import("../../src/types").JobMessage[] = []; const env = createTestEnv({