diff --git a/src/queue/processors.ts b/src/queue/processors.ts index d2ca9c7291..eb7777fd3d 100644 --- a/src/queue/processors.ts +++ b/src/queue/processors.ts @@ -4044,11 +4044,12 @@ async function maybeReReviewOnLinkedIssueChange( if (isConvergenceRepoAllowed(env, repoFullName)) { const openPullRequests = await listOpenPullRequests(env, repoFullName); // Issue-side label/assignment changes can flip linked-issue hard-rule verdicts from mergeable to close. - // Wake every linked open PR immediately: silently dropping a tail here can leave stale passing gates on PRs - // that now violate deterministic issue hard rules. Stagger the jobs so the expensive re-gates do not run inline - // or stampede the queue consumer. + // Wake a rate-aware leading batch promptly; the staleness-ordered sweep still converges any linked tail + // without letting one issue webhook fan out unbounded foreground re-gates. Stagger the jobs so the expensive + // re-gates do not run inline or stampede the queue consumer. const linkingPrs = openPullRequests .filter((pr) => pr.linkedIssues.includes(issueNumber)) + .slice(0, SWEEP_MAX_PRS) .map((pr) => ({ number: pr.number, createdAt: pr.createdAt ?? null })); for (const [index, pr] of linkingPrs.entries()) { const prNumber = pr.number; diff --git a/test/unit/queue.test.ts b/test/unit/queue.test.ts index 36d54ca3dc..06901214a7 100644 --- a/test/unit/queue.test.ts +++ b/test/unit/queue.test.ts @@ -2660,7 +2660,7 @@ describe("queue processors", () => { ]); }); - it("REGRESSION: issue-side linked PR wake queues every linked PR so hard-rule changes cannot leave stale passing gates", async () => { + it("REGRESSION: issue-side linked PR wake caps linked PR fanout to the sweep budget", async () => { const sent: Array<{ message: import("../../src/types").JobMessage; options?: QueueSendOptions }> = []; const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), @@ -2697,13 +2697,13 @@ describe("queue processors", () => { }); expect(fetchCount).toBe(0); - expect(sent).toHaveLength(SWEEP_MAX_PRS + 2); + expect(sent).toHaveLength(SWEEP_MAX_PRS); expect(sent.map(({ message }) => message)).toEqual( - Array.from({ length: SWEEP_MAX_PRS + 2 }, (_, index) => + Array.from({ length: SWEEP_MAX_PRS }, (_, index) => expect.objectContaining({ type: "agent-regate-pr", repoFullName: "owner/agent-repo", prNumber: index + 1, installationId: 9001 }), ), ); - expect(sent.map(({ options }) => options)).toEqual([undefined, { delaySeconds: 10 }, { delaySeconds: 20 }, { delaySeconds: 30 }, { delaySeconds: 40 }]); + expect(sent.map(({ options }) => options)).toEqual([undefined, { delaySeconds: 10 }, { delaySeconds: 20 }]); }); it("REGRESSION (#2371): a coalesced issue-side signal schedules a trailing re-review so an add-then-remove sequence is never lost", async () => {