Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/queue/processors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 4 additions & 4 deletions test/unit/queue.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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 () => {
Expand Down
Loading