From c63470d6d2c5d8df11f6219ff2c665b8e4771689 Mon Sep 17 00:00:00 2001 From: Lakshmi Tanmay Date: Sat, 7 Nov 2026 08:43:31 +0530 Subject: [PATCH 1/4] fix(server): skip CodeRabbit review acknowledgement wakes --- .../orchestration-v2/pullRequestWatch.test.ts | 84 +++++++++++++++++++ .../src/orchestration-v2/pullRequestWatch.ts | 12 ++- 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/apps/server/src/orchestration-v2/pullRequestWatch.test.ts b/apps/server/src/orchestration-v2/pullRequestWatch.test.ts index eb2ae2756d540..09d63340ed651 100644 --- a/apps/server/src/orchestration-v2/pullRequestWatch.test.ts +++ b/apps/server/src/orchestration-v2/pullRequestWatch.test.ts @@ -80,6 +80,25 @@ const remark = ( const noRemarks: ReadonlyArray = []; +const codeRabbitReply = (status: string, action = "Action performed") => + ` + + + +
+${action} +
+
+
+ +${status} + +> Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. + +
+
+
`; + describe("evaluatePullRequestWatch", () => { it.each([false, true])("baselines already-passed checks (required: %s)", (required) => { const green = detail({ checks: [{ ...check("test", "success"), required }] }); @@ -673,6 +692,71 @@ describe("evaluatePullRequestWatch", () => { assert.deepEqual(evaluatePullRequestWatch(report.next, detail(), comments).changes, []); }); + it("skips CodeRabbit review acknowledgements and wakes once for their outcome edits", () => { + for (const status of ["Review triggered.", "Full review triggered."]) { + const queued = { + ...remark("coderabbitai[bot]", "2026-10-02T12:06:00Z", codeRabbitReply(status)), + kind: "issue-comment" as const, + path: null, + }; + const watching = watch({ headSha: "aaaaaaaaaa", wakes: 2 }); + const progress = evaluatePullRequestWatch(watching, detail(), [queued]); + assert.deepEqual(progress.changes, []); + assert.equal(progress.next.remarksThrough, queued.createdAt); + assert.deepEqual(progress.next.remarkIds, [queued.id]); + assert.equal(progress.next.wakes, 2); + assert.isFalse(progress.exhausted); + assert.deepEqual(evaluatePullRequestWatch(progress.next, detail(), [queued]).changes, []); + + for (const body of [ + codeRabbitReply("Review finished."), + codeRabbitReply("Review rate limited.", "Action not completed"), + ]) { + const outcome = { ...queued, body, editedAt: "2026-10-02T12:07:00Z" }; + const finished = evaluatePullRequestWatch(progress.next, detail(), [outcome]); + assert.deepEqual(finished.changes, [{ kind: "remarks", remarks: [outcome] }]); + assert.equal(finished.next.remarksThrough, outcome.editedAt); + assert.equal(finished.next.wakes, 3); + assert.deepEqual(evaluatePullRequestWatch(finished.next, detail(), [outcome]).changes, []); + } + + const late = remark("reviewer", queued.createdAt); + const mixed = evaluatePullRequestWatch(watching, detail(), [queued, late]); + assert.deepEqual(mixed.changes, [{ kind: "remarks", remarks: [late] }]); + assert.deepEqual(mixed.next.remarkIds, [queued.id, late.id]); + assert.deepEqual(evaluatePullRequestWatch(progress.next, detail(), [queued, late]).changes, [ + { kind: "remarks", remarks: [late] }, + ]); + } + }); + + it("keeps other replies and outcomes actionable beside CodeRabbit review progress", () => { + const bodies = [ + codeRabbitReply("Full review triggered.", "Actions performed"), + codeRabbitReply("Review finished."), + codeRabbitReply("Review rate limited.", "Action not completed"), + codeRabbitReply("Unable to review.", "Action not completed"), + codeRabbitReply("Here is the answer to your question."), + "Review triggered.", + codeRabbitReply("Review triggered.").replace( + "", + "", + ), + `${"x".repeat(1_000)}${codeRabbitReply("Review triggered.")}`, + ]; + const comments = [ + ...bodies.map((body, index) => ({ + ...remark("coderabbitai[bot]", "2026-10-02T12:06:00Z", body), + id: `coderabbit-${index}`, + })), + remark("other-bot[bot]", "2026-10-02T12:06:00Z", bodies[0]), + remark("reviewer", "2026-10-02T12:06:00Z", bodies[0]), + ]; + const report = evaluatePullRequestWatch(watch(), detail(), comments); + assert.deepEqual(report.changes, [{ kind: "remarks", remarks: comments.slice(1) }]); + assert.deepEqual(evaluatePullRequestWatch(report.next, detail(), comments).changes, []); + }); + it("cuts a long remark in the wake message without splitting an emoji", () => { const body = `${"x".repeat(196)}🚀 and more`; const report = evaluatePullRequestWatch(watch(), detail(), [ diff --git a/apps/server/src/orchestration-v2/pullRequestWatch.ts b/apps/server/src/orchestration-v2/pullRequestWatch.ts index e12d9a124df8a..ac509f4b796c2 100644 --- a/apps/server/src/orchestration-v2/pullRequestWatch.ts +++ b/apps/server/src/orchestration-v2/pullRequestWatch.ts @@ -36,6 +36,15 @@ const isFailedCheck = (check: PullRequestCheck) => const workflowRunOf = (url: string | null) => url?.match(/^.*\/actions\/runs\/\d+/)?.[0] ?? null; const approvalKey = (check: PullRequestCheck) => workflowRunOf(check.url) ?? check.name; +function isReviewAcknowledgement(remark: PullRequestComment): boolean { + return ( + remark.author?.login.toLowerCase() === "coderabbitai[bot]" && + /[\s\S]*?Actions? performed<\/summary>\s*(?:
\s*
\s*
\s*)?(?:Full review|Review) triggered\./.test( + remark.body.slice(0, 1_000), + ) + ); +} + export function evaluatePullRequestWatch( watch: ThreadPullRequestWatch, detail: Pick< @@ -187,7 +196,8 @@ export function evaluatePullRequestWatch( remark.author?.login.toLowerCase() !== own ); }); - if (fresh.length > 0) changes.push({ kind: "remarks", remarks: fresh }); + const actionable = fresh.filter((remark) => !isReviewAcknowledgement(remark)); + if (actionable.length > 0) changes.push({ kind: "remarks", remarks: actionable }); const latest = Math.max(through, ...fresh.map((remark) => Date.parse(activeAt(remark)))); const atLatest = fresh.filter((remark) => Date.parse(activeAt(remark)) === latest); const remarksThrough = latest === through ? watch.remarksThrough : activeAt(atLatest[0]!); From 4c163f4c1ca6a60cfb5f5216a89d314f90925f88 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 11 Nov 2026 03:33:11 +0000 Subject: [PATCH 2/4] test(server): trim overlapping CodeRabbit acknowledgement cases AI-assisted; verified with the tests and tools listed above. --- apps/server/src/orchestration-v2/pullRequestWatch.test.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/server/src/orchestration-v2/pullRequestWatch.test.ts b/apps/server/src/orchestration-v2/pullRequestWatch.test.ts index 09d63340ed651..b6379b0b3cd3c 100644 --- a/apps/server/src/orchestration-v2/pullRequestWatch.test.ts +++ b/apps/server/src/orchestration-v2/pullRequestWatch.test.ts @@ -733,8 +733,6 @@ describe("evaluatePullRequestWatch", () => { it("keeps other replies and outcomes actionable beside CodeRabbit review progress", () => { const bodies = [ codeRabbitReply("Full review triggered.", "Actions performed"), - codeRabbitReply("Review finished."), - codeRabbitReply("Review rate limited.", "Action not completed"), codeRabbitReply("Unable to review.", "Action not completed"), codeRabbitReply("Here is the answer to your question."), "Review triggered.", @@ -742,7 +740,6 @@ describe("evaluatePullRequestWatch", () => { "", "", ), - `${"x".repeat(1_000)}${codeRabbitReply("Review triggered.")}`, ]; const comments = [ ...bodies.map((body, index) => ({ From bfd70ec599e6c91bdd14678244f69530d3b66b44 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 11 Nov 2026 03:50:39 +0000 Subject: [PATCH 3/4] fix(server): match CodeRabbit acknowledgements with an emoji summary AI-assisted; verified with the tests and tools listed above. --- .../src/orchestration-v2/pullRequestWatch.test.ts | 15 +++++++++++++-- .../src/orchestration-v2/pullRequestWatch.ts | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/apps/server/src/orchestration-v2/pullRequestWatch.test.ts b/apps/server/src/orchestration-v2/pullRequestWatch.test.ts index b6379b0b3cd3c..08d6b0ebe615e 100644 --- a/apps/server/src/orchestration-v2/pullRequestWatch.test.ts +++ b/apps/server/src/orchestration-v2/pullRequestWatch.test.ts @@ -693,9 +693,20 @@ describe("evaluatePullRequestWatch", () => { }); it("skips CodeRabbit review acknowledgements and wakes once for their outcome edits", () => { - for (const status of ["Review triggered.", "Full review triggered."]) { + const current = ` +
+✅ Actions performed + +Review triggered. + +
`; + for (const acknowledgement of [ + codeRabbitReply("Review triggered."), + codeRabbitReply("Full review triggered."), + current, + ]) { const queued = { - ...remark("coderabbitai[bot]", "2026-10-02T12:06:00Z", codeRabbitReply(status)), + ...remark("coderabbitai[bot]", "2026-10-02T12:06:00Z", acknowledgement), kind: "issue-comment" as const, path: null, }; diff --git a/apps/server/src/orchestration-v2/pullRequestWatch.ts b/apps/server/src/orchestration-v2/pullRequestWatch.ts index ac509f4b796c2..2be3f2a22d59e 100644 --- a/apps/server/src/orchestration-v2/pullRequestWatch.ts +++ b/apps/server/src/orchestration-v2/pullRequestWatch.ts @@ -39,7 +39,7 @@ const approvalKey = (check: PullRequestCheck) => workflowRunOf(check.url) ?? che function isReviewAcknowledgement(remark: PullRequestComment): boolean { return ( remark.author?.login.toLowerCase() === "coderabbitai[bot]" && - /[\s\S]*?Actions? performed<\/summary>\s*(?:
\s*
\s*
\s*)?(?:Full review|Review) triggered\./.test( + /[\s\S]*?[^\p{L}<]*Actions? performed<\/summary>\s*(?:
\s*
\s*
\s*)?(?:Full review|Review) triggered\./u.test( remark.body.slice(0, 1_000), ) ); From 25072084aee769f8e3088c7a02493e0e6cff2e31 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 11 Nov 2026 04:01:39 +0000 Subject: [PATCH 4/4] fix(server): match CodeRabbit's login without the [bot] suffix GitHub's GraphQL API and GitLab name the bot "coderabbitai". AI-assisted; verified with the tests and tools listed above. --- .../src/orchestration-v2/pullRequestWatch.test.ts | 14 ++++++++------ .../src/orchestration-v2/pullRequestWatch.ts | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/server/src/orchestration-v2/pullRequestWatch.test.ts b/apps/server/src/orchestration-v2/pullRequestWatch.test.ts index 08d6b0ebe615e..1661673a0cb8f 100644 --- a/apps/server/src/orchestration-v2/pullRequestWatch.test.ts +++ b/apps/server/src/orchestration-v2/pullRequestWatch.test.ts @@ -700,13 +700,15 @@ describe("evaluatePullRequestWatch", () => { Review triggered. `; - for (const acknowledgement of [ - codeRabbitReply("Review triggered."), - codeRabbitReply("Full review triggered."), - current, - ]) { + // GitHub's GraphQL API names bots without the "[bot]" suffix that its REST API adds. + for (const [login, acknowledgement] of [ + ["coderabbitai[bot]", codeRabbitReply("Review triggered.")], + ["coderabbitai[bot]", codeRabbitReply("Full review triggered.")], + ["coderabbitai[bot]", current], + ["coderabbitai", current], + ] as const) { const queued = { - ...remark("coderabbitai[bot]", "2026-10-02T12:06:00Z", acknowledgement), + ...remark(login, "2026-10-02T12:06:00Z", acknowledgement), kind: "issue-comment" as const, path: null, }; diff --git a/apps/server/src/orchestration-v2/pullRequestWatch.ts b/apps/server/src/orchestration-v2/pullRequestWatch.ts index 2be3f2a22d59e..683f61cae010d 100644 --- a/apps/server/src/orchestration-v2/pullRequestWatch.ts +++ b/apps/server/src/orchestration-v2/pullRequestWatch.ts @@ -38,7 +38,7 @@ const approvalKey = (check: PullRequestCheck) => workflowRunOf(check.url) ?? che function isReviewAcknowledgement(remark: PullRequestComment): boolean { return ( - remark.author?.login.toLowerCase() === "coderabbitai[bot]" && + remark.author?.login.toLowerCase().replace(/\[bot\]$/, "") === "coderabbitai" && /[\s\S]*?[^\p{L}<]*Actions? performed<\/summary>\s*(?:
\s*
\s*
\s*)?(?:Full review|Review) triggered\./u.test( remark.body.slice(0, 1_000), )