From 2868f85c3e44e95848aed5c051a302c2de61fc9b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:51:28 +0000 Subject: [PATCH 1/4] Initial plan From f96455aa8fd57c6c048ffe03a3499ae8ca74cc53 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Jul 2026 21:16:15 +0000 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20downgrade=20REQUEST=5FCHANGES?= =?UTF-8?q?=E2=86=92COMMENT=20on=20self-authored=20PRs=20in=20safe-outputs?= =?UTF-8?q?=20PR=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes 422 "Can not request changes on your own pull request" failures that occur when the PR Code Quality Reviewer (or any reviewer workflow) runs against a PR authored by the same bot identity (e.g. linter-miner branches). Three complementary defences in pr_review_buffer.cjs: 1. Proactive check: before building requestParams, compare pullRequest.user.login against GITHUB_ACTOR. If they match, downgrade REQUEST_CHANGES/APPROVE → COMMENT immediately, avoiding the 422 entirely. 2. Own-PR COMMENT retry → body-only COMMENT fallback: when the own-PR COMMENT retry (reactive path) also fails with "Line could not be resolved" or "Path could not be resolved", fall back to a body-only COMMENT review instead of hard-failing. 3. Body-only fallback own-PR guard: when the "Line could not be resolved" body-only fallback itself gets a 422 "Can not request changes on your own pull request", downgrade the body-only event to COMMENT and retry — this is the exact scenario that caused the 4/4 safe-output failures in runs 29946005059 and 29910168345. Six new tests cover all three paths. Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/pr_review_buffer.cjs | 61 ++++++- actions/setup/js/pr_review_buffer.test.cjs | 203 +++++++++++++++++++++ 2 files changed, 257 insertions(+), 7 deletions(-) diff --git a/actions/setup/js/pr_review_buffer.cjs b/actions/setup/js/pr_review_buffer.cjs index 1417445f2ab..ef15dc2e33c 100644 --- a/actions/setup/js/pr_review_buffer.cjs +++ b/actions/setup/js/pr_review_buffer.cjs @@ -302,6 +302,18 @@ function createReviewBuffer() { let event = reviewMetadata ? reviewMetadata.event : "COMMENT"; let body = reviewMetadata ? reviewMetadata.body : ""; + // Proactively downgrade REQUEST_CHANGES/APPROVE → COMMENT on self-authored PRs. + // GITHUB_TOKEN lacks read:user scope for reliable identity pre-flight via the API, so + // compare the PR author login against GITHUB_ACTOR as a best-effort guard. + // The reactive 422 retry in the catch block is retained as a safety net for cases this misses. + if (event !== "COMMENT" && pullRequest.user?.login) { + const actor = (process.env.GITHUB_ACTOR || "").trim(); + if (actor && pullRequest.user.login === actor) { + core.warning(`PR #${pullRequestNumber} is authored by the workflow actor ('${actor}'). ` + `Downgrading '${event}' → 'COMMENT' to avoid 422 "Can not request changes on your own pull request".`); + event = "COMMENT"; + } + } + // Determine if we should add footer based on footer mode let shouldAddFooter = footerMode === "always"; if (footerMode === "if-body") { @@ -620,10 +632,29 @@ function createReviewBuffer() { core.info(`Created PR review #${review.id}: ${review.html_url}`); return buildReviewSuccessResult(review, "COMMENT", comments.length, afterState); } catch (retryError) { - core.error(`Failed to submit PR review on retry: ${getErrorMessage(retryError)}`); + const retryErrorMsg = getErrorMessage(retryError); + // If the COMMENT retry still fails due to unresolvable line(s), fall back to body-only COMMENT. + if (retryErrorMsg.includes("Line could not be resolved") || retryErrorMsg.includes("Path could not be resolved")) { + core.warning(`COMMENT retry on own PR failed with unresolvable line(s): ${retryErrorMsg}. Falling back to body-only COMMENT.`); + try { + const ownPrBodyOnlyParams = { ...requestParams }; + delete ownPrBodyOnlyParams.comments; + ownPrBodyOnlyParams.event = "COMMENT"; + ownPrBodyOnlyParams.body = appendUnanchoredCommentsSection(typeof requestParams.body === "string" ? requestParams.body : "", comments); + const { data: review } = await createReviewWithRetry(ownPrBodyOnlyParams); + await maybeSupersedeOlderReviews(review.id); + const afterState = await fetchAfterStateIfAvailable(); + core.info(`Created PR review #${review.id} (own-PR body-only COMMENT): ${review.html_url}`); + return buildReviewSuccessResult(review, "COMMENT", 0, afterState); + } catch (bodyOnlyError) { + core.error(`Failed to submit body-only COMMENT review: ${getErrorMessage(bodyOnlyError)}`); + return { success: false, error: getErrorMessage(bodyOnlyError) }; + } + } + core.error(`Failed to submit PR review on retry: ${retryErrorMsg}`); return { success: false, - error: getErrorMessage(retryError), + error: retryErrorMsg, }; } } @@ -691,20 +722,36 @@ function createReviewBuffer() { } core.warning(`PR review submission failed due to unresolvable comment line(s): ${errorMessage}. Retrying as body-only review.`); + const bodyOnlyParams = { ...requestParams }; + delete bodyOnlyParams.comments; + bodyOnlyParams.body = appendUnanchoredCommentsSection(typeof requestParams.body === "string" ? requestParams.body : "", comments); try { - const bodyOnlyParams = { ...requestParams }; - delete bodyOnlyParams.comments; - bodyOnlyParams.body = appendUnanchoredCommentsSection(typeof requestParams.body === "string" ? requestParams.body : "", comments); const { data: review } = await createReviewWithRetry(bodyOnlyParams); await maybeSupersedeOlderReviews(review.id); const afterState = await fetchAfterStateIfAvailable(); core.info(`Created PR review #${review.id} (body-only fallback): ${review.html_url}`); return buildReviewSuccessResult(review, event, 0, afterState); } catch (retryError) { - core.error(`Failed to submit body-only PR review: ${getErrorMessage(retryError)}`); + const retryErrorMsg = getErrorMessage(retryError); + // If body-only also fails because it's a self-authored PR, retry as body-only COMMENT. + if (bodyOnlyParams.event !== "COMMENT" && ownPrMessages.some(msg => retryErrorMsg.includes(msg))) { + core.warning(`Body-only ${bodyOnlyParams.event} review rejected on own PR. Retrying as body-only COMMENT.`); + try { + bodyOnlyParams.event = "COMMENT"; + const { data: review } = await createReviewWithRetry(bodyOnlyParams); + await maybeSupersedeOlderReviews(review.id); + const afterState = await fetchAfterStateIfAvailable(); + core.info(`Created PR review #${review.id} (body-only COMMENT fallback): ${review.html_url}`); + return buildReviewSuccessResult(review, "COMMENT", 0, afterState); + } catch (ownPrRetryError) { + core.error(`Failed to submit body-only COMMENT review: ${getErrorMessage(ownPrRetryError)}`); + return { success: false, error: getErrorMessage(ownPrRetryError) }; + } + } + core.error(`Failed to submit body-only PR review: ${retryErrorMsg}`); return { success: false, - error: getErrorMessage(retryError), + error: retryErrorMsg, }; } } diff --git a/actions/setup/js/pr_review_buffer.test.cjs b/actions/setup/js/pr_review_buffer.test.cjs index 8cf7c8a2126..3b0a6430047 100644 --- a/actions/setup/js/pr_review_buffer.test.cjs +++ b/actions/setup/js/pr_review_buffer.test.cjs @@ -896,6 +896,117 @@ describe("pr_review_buffer (factory pattern)", () => { expect(callArgs.event).toBe("APPROVE"); }); + it("should proactively downgrade REQUEST_CHANGES → COMMENT when PR author matches GITHUB_ACTOR", async () => { + const previousActor = process.env.GITHUB_ACTOR; + process.env.GITHUB_ACTOR = "linter-miner[bot]"; + try { + buffer.addComment({ path: "test.js", line: 1, body: "comment" }); + buffer.setReviewMetadata("Fix this", "REQUEST_CHANGES"); + buffer.setReviewContext({ + repo: "owner/repo", + repoParts: { owner: "owner", repo: "repo" }, + pullRequestNumber: 42, + pullRequest: { head: { sha: "abc123" }, user: { login: "linter-miner[bot]" } }, + }); + + mockGithub.rest.pulls.createReview.mockResolvedValueOnce({ + data: { + id: 703, + html_url: "https://github.com/owner/repo/pull/42#pullrequestreview-703", + }, + }); + + const result = await buffer.submitReview(); + + expect(result.success).toBe(true); + expect(result.event).toBe("COMMENT"); + // Only one call — no 422 because we downgraded proactively + expect(mockGithub.rest.pulls.createReview).toHaveBeenCalledTimes(1); + const callArgs = mockGithub.rest.pulls.createReview.mock.calls[0][0]; + expect(callArgs.event).toBe("COMMENT"); + expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("Downgrading 'REQUEST_CHANGES' → 'COMMENT'")); + } finally { + if (previousActor === undefined) { + delete process.env.GITHUB_ACTOR; + } else { + process.env.GITHUB_ACTOR = previousActor; + } + } + }); + + it("should proactively downgrade APPROVE → COMMENT when PR author matches GITHUB_ACTOR", async () => { + const previousActor = process.env.GITHUB_ACTOR; + process.env.GITHUB_ACTOR = "bot-user"; + try { + buffer.addComment({ path: "test.js", line: 1, body: "comment" }); + buffer.setReviewMetadata("LGTM", "APPROVE"); + buffer.setReviewContext({ + repo: "owner/repo", + repoParts: { owner: "owner", repo: "repo" }, + pullRequestNumber: 42, + pullRequest: { head: { sha: "abc123" }, user: { login: "bot-user" } }, + }); + + mockGithub.rest.pulls.createReview.mockResolvedValueOnce({ + data: { + id: 704, + html_url: "https://github.com/owner/repo/pull/42#pullrequestreview-704", + }, + }); + + const result = await buffer.submitReview(); + + expect(result.success).toBe(true); + expect(result.event).toBe("COMMENT"); + expect(mockGithub.rest.pulls.createReview).toHaveBeenCalledTimes(1); + const callArgs = mockGithub.rest.pulls.createReview.mock.calls[0][0]; + expect(callArgs.event).toBe("COMMENT"); + expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("Downgrading 'APPROVE' → 'COMMENT'")); + } finally { + if (previousActor === undefined) { + delete process.env.GITHUB_ACTOR; + } else { + process.env.GITHUB_ACTOR = previousActor; + } + } + }); + + it("should not downgrade when PR author does not match GITHUB_ACTOR", async () => { + const previousActor = process.env.GITHUB_ACTOR; + process.env.GITHUB_ACTOR = "reviewer-bot"; + try { + buffer.addComment({ path: "test.js", line: 1, body: "comment" }); + buffer.setReviewMetadata("Fix this", "REQUEST_CHANGES"); + buffer.setReviewContext({ + repo: "owner/repo", + repoParts: { owner: "owner", repo: "repo" }, + pullRequestNumber: 42, + pullRequest: { head: { sha: "abc123" }, user: { login: "pr-author" } }, + }); + + mockGithub.rest.pulls.createReview.mockResolvedValueOnce({ + data: { + id: 705, + html_url: "https://github.com/owner/repo/pull/42#pullrequestreview-705", + }, + }); + + const result = await buffer.submitReview(); + + expect(result.success).toBe(true); + expect(result.event).toBe("REQUEST_CHANGES"); + expect(mockGithub.rest.pulls.createReview).toHaveBeenCalledTimes(1); + const callArgs = mockGithub.rest.pulls.createReview.mock.calls[0][0]; + expect(callArgs.event).toBe("REQUEST_CHANGES"); + } finally { + if (previousActor === undefined) { + delete process.env.GITHUB_ACTOR; + } else { + process.env.GITHUB_ACTOR = previousActor; + } + } + }); + it("should not retry with COMMENT when event is already COMMENT and API error occurs", async () => { buffer.addComment({ path: "test.js", line: 1, body: "comment" }); buffer.setReviewMetadata("Some feedback", "COMMENT"); @@ -933,6 +1044,41 @@ describe("pr_review_buffer (factory pattern)", () => { expect(mockGithub.rest.pulls.createReview).toHaveBeenCalledTimes(2); }); + it("should fall back to body-only COMMENT when own-PR COMMENT retry also fails with line-resolution error", async () => { + buffer.addComment({ path: "file.go", line: 10, body: "Inline comment" }); + buffer.setReviewMetadata("Fix this", "REQUEST_CHANGES"); + buffer.setReviewContext({ + repo: "owner/repo", + repoParts: { owner: "owner", repo: "repo" }, + pullRequestNumber: 42, + pullRequest: { head: { sha: "abc123" }, user: { login: "bot-user" } }, + }); + + mockGithub.rest.pulls.createReview + // Initial attempt — own-PR 422 + .mockRejectedValueOnce(new Error("Can not request changes on your own pull request")) + // COMMENT retry with inline comments — line unresolvable + .mockRejectedValueOnce(new Error('Unprocessable Entity: "Line could not be resolved"')) + // Body-only COMMENT fallback — success + .mockResolvedValueOnce({ + data: { + id: 800, + html_url: "https://github.com/owner/repo/pull/42#pullrequestreview-800", + }, + }); + + const result = await buffer.submitReview(); + + expect(result.success).toBe(true); + expect(result.event).toBe("COMMENT"); + expect(mockGithub.rest.pulls.createReview).toHaveBeenCalledTimes(3); + const bodyOnlyArgs = mockGithub.rest.pulls.createReview.mock.calls[2][0]; + expect(bodyOnlyArgs.event).toBe("COMMENT"); + expect(bodyOnlyArgs.comments).toBeUndefined(); + expect(bodyOnlyArgs.body).toContain("Inline comment"); + expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("COMMENT retry on own PR failed with unresolvable line(s)")); + }); + it("should skip (success:true, skipped:true) when PR is permanently locked after all retries", async () => { const setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation(handler => { if (typeof handler === "function") { @@ -1417,6 +1563,63 @@ describe("pr_review_buffer (factory pattern)", () => { expect(mockGithub.rest.pulls.createReview).toHaveBeenCalledTimes(2); }); + it("should retry body-only as COMMENT when body-only REQUEST_CHANGES is rejected on own PR after line-resolution failure", async () => { + buffer.addComment({ path: "file.go", line: 5, body: "Nil dereference on this line" }); + buffer.setReviewMetadata("Fix the blocking issues.", "REQUEST_CHANGES"); + buffer.setReviewContext({ + repo: "owner/repo", + repoParts: { owner: "owner", repo: "repo" }, + pullRequestNumber: 47375, + pullRequest: { head: { sha: "abc123" }, user: { login: "linter-miner[bot]" } }, + }); + + mockGithub.rest.pulls.createReview + // Initial attempt — line resolution fails (this triggers the body-only path) + .mockRejectedValueOnce(new Error('Unprocessable Entity: "Line could not be resolved"')) + // Body-only REQUEST_CHANGES — own-PR 422 + .mockRejectedValueOnce(new Error("Can not request changes on your own pull request")) + // Body-only COMMENT retry — success + .mockResolvedValueOnce({ + data: { + id: 900, + html_url: "https://github.com/owner/repo/pull/47375#pullrequestreview-900", + }, + }); + + const result = await buffer.submitReview(); + + expect(result.success).toBe(true); + expect(result.event).toBe("COMMENT"); + expect(mockGithub.rest.pulls.createReview).toHaveBeenCalledTimes(3); + const bodyOnlyCommentArgs = mockGithub.rest.pulls.createReview.mock.calls[2][0]; + expect(bodyOnlyCommentArgs.event).toBe("COMMENT"); + expect(bodyOnlyCommentArgs.comments).toBeUndefined(); + expect(bodyOnlyCommentArgs.body).toContain("Nil dereference on this line"); + expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("Body-only REQUEST_CHANGES review rejected on own PR")); + }); + + it("should return failure when body-only COMMENT retry also fails on own PR", async () => { + buffer.addComment({ path: "file.go", line: 5, body: "Review comment" }); + buffer.setReviewMetadata("Feedback.", "REQUEST_CHANGES"); + buffer.setReviewContext({ + repo: "owner/repo", + repoParts: { owner: "owner", repo: "repo" }, + pullRequestNumber: 42, + pullRequest: { head: { sha: "abc123" } }, + }); + + mockGithub.rest.pulls.createReview + .mockRejectedValueOnce(new Error("Line could not be resolved")) + .mockRejectedValueOnce(new Error("Can not request changes on your own pull request")) + .mockRejectedValueOnce(new Error("Unexpected server error")); + + const result = await buffer.submitReview(); + + expect(result.success).toBe(false); + expect(result.error).toContain("Unexpected server error"); + expect(mockGithub.rest.pulls.createReview).toHaveBeenCalledTimes(3); + }); + it("should escape HTML-sensitive characters in fallback summary and body", async () => { buffer.addComment({ path: "src/&\"'.js", From 8f84f653303d6fc848a4d4433d6f69c8fabd357d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Jul 2026 22:19:49 +0000 Subject: [PATCH 3/4] fix: remove self-author actor heuristic in PR review buffer Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- actions/setup/js/pr_review_buffer.cjs | 12 - actions/setup/js/pr_review_buffer.test.cjs | 243 +++++++-------------- 2 files changed, 81 insertions(+), 174 deletions(-) diff --git a/actions/setup/js/pr_review_buffer.cjs b/actions/setup/js/pr_review_buffer.cjs index ef15dc2e33c..3a3e3cf2a51 100644 --- a/actions/setup/js/pr_review_buffer.cjs +++ b/actions/setup/js/pr_review_buffer.cjs @@ -302,18 +302,6 @@ function createReviewBuffer() { let event = reviewMetadata ? reviewMetadata.event : "COMMENT"; let body = reviewMetadata ? reviewMetadata.body : ""; - // Proactively downgrade REQUEST_CHANGES/APPROVE → COMMENT on self-authored PRs. - // GITHUB_TOKEN lacks read:user scope for reliable identity pre-flight via the API, so - // compare the PR author login against GITHUB_ACTOR as a best-effort guard. - // The reactive 422 retry in the catch block is retained as a safety net for cases this misses. - if (event !== "COMMENT" && pullRequest.user?.login) { - const actor = (process.env.GITHUB_ACTOR || "").trim(); - if (actor && pullRequest.user.login === actor) { - core.warning(`PR #${pullRequestNumber} is authored by the workflow actor ('${actor}'). ` + `Downgrading '${event}' → 'COMMENT' to avoid 422 "Can not request changes on your own pull request".`); - event = "COMMENT"; - } - } - // Determine if we should add footer based on footer mode let shouldAddFooter = footerMode === "always"; if (footerMode === "if-body") { diff --git a/actions/setup/js/pr_review_buffer.test.cjs b/actions/setup/js/pr_review_buffer.test.cjs index 3b0a6430047..850d4754ad4 100644 --- a/actions/setup/js/pr_review_buffer.test.cjs +++ b/actions/setup/js/pr_review_buffer.test.cjs @@ -896,47 +896,27 @@ describe("pr_review_buffer (factory pattern)", () => { expect(callArgs.event).toBe("APPROVE"); }); - it("should proactively downgrade REQUEST_CHANGES → COMMENT when PR author matches GITHUB_ACTOR", async () => { - const previousActor = process.env.GITHUB_ACTOR; - process.env.GITHUB_ACTOR = "linter-miner[bot]"; - try { - buffer.addComment({ path: "test.js", line: 1, body: "comment" }); - buffer.setReviewMetadata("Fix this", "REQUEST_CHANGES"); - buffer.setReviewContext({ - repo: "owner/repo", - repoParts: { owner: "owner", repo: "repo" }, - pullRequestNumber: 42, - pullRequest: { head: { sha: "abc123" }, user: { login: "linter-miner[bot]" } }, - }); + it("should not retry with COMMENT when event is already COMMENT and API error occurs", async () => { + buffer.addComment({ path: "test.js", line: 1, body: "comment" }); + buffer.setReviewMetadata("Some feedback", "COMMENT"); + buffer.setReviewContext({ + repo: "owner/repo", + repoParts: { owner: "owner", repo: "repo" }, + pullRequestNumber: 42, + pullRequest: { head: { sha: "abc123" }, user: { login: "bot-user" } }, + }); - mockGithub.rest.pulls.createReview.mockResolvedValueOnce({ - data: { - id: 703, - html_url: "https://github.com/owner/repo/pull/42#pullrequestreview-703", - }, - }); + mockGithub.rest.pulls.createReview.mockRejectedValue(new Error("Validation Failed")); - const result = await buffer.submitReview(); + const result = await buffer.submitReview(); - expect(result.success).toBe(true); - expect(result.event).toBe("COMMENT"); - // Only one call — no 422 because we downgraded proactively - expect(mockGithub.rest.pulls.createReview).toHaveBeenCalledTimes(1); - const callArgs = mockGithub.rest.pulls.createReview.mock.calls[0][0]; - expect(callArgs.event).toBe("COMMENT"); - expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("Downgrading 'REQUEST_CHANGES' → 'COMMENT'")); - } finally { - if (previousActor === undefined) { - delete process.env.GITHUB_ACTOR; - } else { - process.env.GITHUB_ACTOR = previousActor; - } - } + expect(result.success).toBe(false); + expect(mockGithub.rest.pulls.createReview).toHaveBeenCalledTimes(1); }); - it("should proactively downgrade APPROVE → COMMENT when PR author matches GITHUB_ACTOR", async () => { + it("should return failure when retry also fails", async () => { const previousActor = process.env.GITHUB_ACTOR; - process.env.GITHUB_ACTOR = "bot-user"; + process.env.GITHUB_ACTOR = "someone-else"; try { buffer.addComment({ path: "test.js", line: 1, body: "comment" }); buffer.setReviewMetadata("LGTM", "APPROVE"); @@ -947,21 +927,13 @@ describe("pr_review_buffer (factory pattern)", () => { pullRequest: { head: { sha: "abc123" }, user: { login: "bot-user" } }, }); - mockGithub.rest.pulls.createReview.mockResolvedValueOnce({ - data: { - id: 704, - html_url: "https://github.com/owner/repo/pull/42#pullrequestreview-704", - }, - }); + mockGithub.rest.pulls.createReview.mockRejectedValueOnce(new Error("Can not approve your own pull request")).mockRejectedValueOnce(new Error("Some other error")); const result = await buffer.submitReview(); - expect(result.success).toBe(true); - expect(result.event).toBe("COMMENT"); - expect(mockGithub.rest.pulls.createReview).toHaveBeenCalledTimes(1); - const callArgs = mockGithub.rest.pulls.createReview.mock.calls[0][0]; - expect(callArgs.event).toBe("COMMENT"); - expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("Downgrading 'APPROVE' → 'COMMENT'")); + expect(result.success).toBe(false); + expect(result.error).toContain("Some other error"); + expect(mockGithub.rest.pulls.createReview).toHaveBeenCalledTimes(2); } finally { if (previousActor === undefined) { delete process.env.GITHUB_ACTOR; @@ -971,33 +943,42 @@ describe("pr_review_buffer (factory pattern)", () => { } }); - it("should not downgrade when PR author does not match GITHUB_ACTOR", async () => { + it("should fall back to body-only COMMENT when own-PR COMMENT retry also fails with line-resolution error", async () => { const previousActor = process.env.GITHUB_ACTOR; - process.env.GITHUB_ACTOR = "reviewer-bot"; + process.env.GITHUB_ACTOR = "someone-else"; try { - buffer.addComment({ path: "test.js", line: 1, body: "comment" }); + buffer.addComment({ path: "file.go", line: 10, body: "Inline comment" }); buffer.setReviewMetadata("Fix this", "REQUEST_CHANGES"); buffer.setReviewContext({ repo: "owner/repo", repoParts: { owner: "owner", repo: "repo" }, pullRequestNumber: 42, - pullRequest: { head: { sha: "abc123" }, user: { login: "pr-author" } }, + pullRequest: { head: { sha: "abc123" }, user: { login: "bot-user" } }, }); - mockGithub.rest.pulls.createReview.mockResolvedValueOnce({ - data: { - id: 705, - html_url: "https://github.com/owner/repo/pull/42#pullrequestreview-705", - }, - }); + mockGithub.rest.pulls.createReview + // Initial attempt — own-PR 422 + .mockRejectedValueOnce(new Error("Can not request changes on your own pull request")) + // COMMENT retry with inline comments — line unresolvable + .mockRejectedValueOnce(new Error('Unprocessable Entity: "Line could not be resolved"')) + // Body-only COMMENT fallback — success + .mockResolvedValueOnce({ + data: { + id: 800, + html_url: "https://github.com/owner/repo/pull/42#pullrequestreview-800", + }, + }); const result = await buffer.submitReview(); expect(result.success).toBe(true); - expect(result.event).toBe("REQUEST_CHANGES"); - expect(mockGithub.rest.pulls.createReview).toHaveBeenCalledTimes(1); - const callArgs = mockGithub.rest.pulls.createReview.mock.calls[0][0]; - expect(callArgs.event).toBe("REQUEST_CHANGES"); + expect(result.event).toBe("COMMENT"); + expect(mockGithub.rest.pulls.createReview).toHaveBeenCalledTimes(3); + const bodyOnlyArgs = mockGithub.rest.pulls.createReview.mock.calls[2][0]; + expect(bodyOnlyArgs.event).toBe("COMMENT"); + expect(bodyOnlyArgs.comments).toBeUndefined(); + expect(bodyOnlyArgs.body).toContain("Inline comment"); + expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("COMMENT retry on own PR failed with unresolvable line(s)")); } finally { if (previousActor === undefined) { delete process.env.GITHUB_ACTOR; @@ -1007,78 +988,6 @@ describe("pr_review_buffer (factory pattern)", () => { } }); - it("should not retry with COMMENT when event is already COMMENT and API error occurs", async () => { - buffer.addComment({ path: "test.js", line: 1, body: "comment" }); - buffer.setReviewMetadata("Some feedback", "COMMENT"); - buffer.setReviewContext({ - repo: "owner/repo", - repoParts: { owner: "owner", repo: "repo" }, - pullRequestNumber: 42, - pullRequest: { head: { sha: "abc123" }, user: { login: "bot-user" } }, - }); - - mockGithub.rest.pulls.createReview.mockRejectedValue(new Error("Validation Failed")); - - const result = await buffer.submitReview(); - - expect(result.success).toBe(false); - expect(mockGithub.rest.pulls.createReview).toHaveBeenCalledTimes(1); - }); - - it("should return failure when retry also fails", async () => { - buffer.addComment({ path: "test.js", line: 1, body: "comment" }); - buffer.setReviewMetadata("LGTM", "APPROVE"); - buffer.setReviewContext({ - repo: "owner/repo", - repoParts: { owner: "owner", repo: "repo" }, - pullRequestNumber: 42, - pullRequest: { head: { sha: "abc123" }, user: { login: "bot-user" } }, - }); - - mockGithub.rest.pulls.createReview.mockRejectedValueOnce(new Error("Can not approve your own pull request")).mockRejectedValueOnce(new Error("Some other error")); - - const result = await buffer.submitReview(); - - expect(result.success).toBe(false); - expect(result.error).toContain("Some other error"); - expect(mockGithub.rest.pulls.createReview).toHaveBeenCalledTimes(2); - }); - - it("should fall back to body-only COMMENT when own-PR COMMENT retry also fails with line-resolution error", async () => { - buffer.addComment({ path: "file.go", line: 10, body: "Inline comment" }); - buffer.setReviewMetadata("Fix this", "REQUEST_CHANGES"); - buffer.setReviewContext({ - repo: "owner/repo", - repoParts: { owner: "owner", repo: "repo" }, - pullRequestNumber: 42, - pullRequest: { head: { sha: "abc123" }, user: { login: "bot-user" } }, - }); - - mockGithub.rest.pulls.createReview - // Initial attempt — own-PR 422 - .mockRejectedValueOnce(new Error("Can not request changes on your own pull request")) - // COMMENT retry with inline comments — line unresolvable - .mockRejectedValueOnce(new Error('Unprocessable Entity: "Line could not be resolved"')) - // Body-only COMMENT fallback — success - .mockResolvedValueOnce({ - data: { - id: 800, - html_url: "https://github.com/owner/repo/pull/42#pullrequestreview-800", - }, - }); - - const result = await buffer.submitReview(); - - expect(result.success).toBe(true); - expect(result.event).toBe("COMMENT"); - expect(mockGithub.rest.pulls.createReview).toHaveBeenCalledTimes(3); - const bodyOnlyArgs = mockGithub.rest.pulls.createReview.mock.calls[2][0]; - expect(bodyOnlyArgs.event).toBe("COMMENT"); - expect(bodyOnlyArgs.comments).toBeUndefined(); - expect(bodyOnlyArgs.body).toContain("Inline comment"); - expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("COMMENT retry on own PR failed with unresolvable line(s)")); - }); - it("should skip (success:true, skipped:true) when PR is permanently locked after all retries", async () => { const setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation(handler => { if (typeof handler === "function") { @@ -1564,38 +1473,48 @@ describe("pr_review_buffer (factory pattern)", () => { }); it("should retry body-only as COMMENT when body-only REQUEST_CHANGES is rejected on own PR after line-resolution failure", async () => { - buffer.addComment({ path: "file.go", line: 5, body: "Nil dereference on this line" }); - buffer.setReviewMetadata("Fix the blocking issues.", "REQUEST_CHANGES"); - buffer.setReviewContext({ - repo: "owner/repo", - repoParts: { owner: "owner", repo: "repo" }, - pullRequestNumber: 47375, - pullRequest: { head: { sha: "abc123" }, user: { login: "linter-miner[bot]" } }, - }); - - mockGithub.rest.pulls.createReview - // Initial attempt — line resolution fails (this triggers the body-only path) - .mockRejectedValueOnce(new Error('Unprocessable Entity: "Line could not be resolved"')) - // Body-only REQUEST_CHANGES — own-PR 422 - .mockRejectedValueOnce(new Error("Can not request changes on your own pull request")) - // Body-only COMMENT retry — success - .mockResolvedValueOnce({ - data: { - id: 900, - html_url: "https://github.com/owner/repo/pull/47375#pullrequestreview-900", - }, + const previousActor = process.env.GITHUB_ACTOR; + process.env.GITHUB_ACTOR = "someone-else"; + try { + buffer.addComment({ path: "file.go", line: 5, body: "Nil dereference on this line" }); + buffer.setReviewMetadata("Fix the blocking issues.", "REQUEST_CHANGES"); + buffer.setReviewContext({ + repo: "owner/repo", + repoParts: { owner: "owner", repo: "repo" }, + pullRequestNumber: 47375, + pullRequest: { head: { sha: "abc123" }, user: { login: "linter-miner[bot]" } }, }); - const result = await buffer.submitReview(); + mockGithub.rest.pulls.createReview + // Initial attempt — line resolution fails (this triggers the body-only path) + .mockRejectedValueOnce(new Error('Unprocessable Entity: "Line could not be resolved"')) + // Body-only REQUEST_CHANGES — own-PR 422 + .mockRejectedValueOnce(new Error("Can not request changes on your own pull request")) + // Body-only COMMENT retry — success + .mockResolvedValueOnce({ + data: { + id: 900, + html_url: "https://github.com/owner/repo/pull/47375#pullrequestreview-900", + }, + }); - expect(result.success).toBe(true); - expect(result.event).toBe("COMMENT"); - expect(mockGithub.rest.pulls.createReview).toHaveBeenCalledTimes(3); - const bodyOnlyCommentArgs = mockGithub.rest.pulls.createReview.mock.calls[2][0]; - expect(bodyOnlyCommentArgs.event).toBe("COMMENT"); - expect(bodyOnlyCommentArgs.comments).toBeUndefined(); - expect(bodyOnlyCommentArgs.body).toContain("Nil dereference on this line"); - expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("Body-only REQUEST_CHANGES review rejected on own PR")); + const result = await buffer.submitReview(); + + expect(result.success).toBe(true); + expect(result.event).toBe("COMMENT"); + expect(mockGithub.rest.pulls.createReview).toHaveBeenCalledTimes(3); + const bodyOnlyCommentArgs = mockGithub.rest.pulls.createReview.mock.calls[2][0]; + expect(bodyOnlyCommentArgs.event).toBe("COMMENT"); + expect(bodyOnlyCommentArgs.comments).toBeUndefined(); + expect(bodyOnlyCommentArgs.body).toContain("Nil dereference on this line"); + expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("Body-only REQUEST_CHANGES review rejected on own PR")); + } finally { + if (previousActor === undefined) { + delete process.env.GITHUB_ACTOR; + } else { + process.env.GITHUB_ACTOR = previousActor; + } + } }); it("should return failure when body-only COMMENT retry also fails on own PR", async () => { From bff72c705da71e6c02216f617d42d88f17c255f7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Jul 2026 22:23:52 +0000 Subject: [PATCH 4/4] test: remove redundant actor isolation from PR review tests Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- actions/setup/js/pr_review_buffer.test.cjs | 172 +++++++++------------ 1 file changed, 71 insertions(+), 101 deletions(-) diff --git a/actions/setup/js/pr_review_buffer.test.cjs b/actions/setup/js/pr_review_buffer.test.cjs index 850d4754ad4..31079debc65 100644 --- a/actions/setup/js/pr_review_buffer.test.cjs +++ b/actions/setup/js/pr_review_buffer.test.cjs @@ -915,77 +915,57 @@ describe("pr_review_buffer (factory pattern)", () => { }); it("should return failure when retry also fails", async () => { - const previousActor = process.env.GITHUB_ACTOR; - process.env.GITHUB_ACTOR = "someone-else"; - try { - buffer.addComment({ path: "test.js", line: 1, body: "comment" }); - buffer.setReviewMetadata("LGTM", "APPROVE"); - buffer.setReviewContext({ - repo: "owner/repo", - repoParts: { owner: "owner", repo: "repo" }, - pullRequestNumber: 42, - pullRequest: { head: { sha: "abc123" }, user: { login: "bot-user" } }, - }); + buffer.addComment({ path: "test.js", line: 1, body: "comment" }); + buffer.setReviewMetadata("LGTM", "APPROVE"); + buffer.setReviewContext({ + repo: "owner/repo", + repoParts: { owner: "owner", repo: "repo" }, + pullRequestNumber: 42, + pullRequest: { head: { sha: "abc123" }, user: { login: "bot-user" } }, + }); - mockGithub.rest.pulls.createReview.mockRejectedValueOnce(new Error("Can not approve your own pull request")).mockRejectedValueOnce(new Error("Some other error")); + mockGithub.rest.pulls.createReview.mockRejectedValueOnce(new Error("Can not approve your own pull request")).mockRejectedValueOnce(new Error("Some other error")); - const result = await buffer.submitReview(); + const result = await buffer.submitReview(); - expect(result.success).toBe(false); - expect(result.error).toContain("Some other error"); - expect(mockGithub.rest.pulls.createReview).toHaveBeenCalledTimes(2); - } finally { - if (previousActor === undefined) { - delete process.env.GITHUB_ACTOR; - } else { - process.env.GITHUB_ACTOR = previousActor; - } - } + expect(result.success).toBe(false); + expect(result.error).toContain("Some other error"); + expect(mockGithub.rest.pulls.createReview).toHaveBeenCalledTimes(2); }); it("should fall back to body-only COMMENT when own-PR COMMENT retry also fails with line-resolution error", async () => { - const previousActor = process.env.GITHUB_ACTOR; - process.env.GITHUB_ACTOR = "someone-else"; - try { - buffer.addComment({ path: "file.go", line: 10, body: "Inline comment" }); - buffer.setReviewMetadata("Fix this", "REQUEST_CHANGES"); - buffer.setReviewContext({ - repo: "owner/repo", - repoParts: { owner: "owner", repo: "repo" }, - pullRequestNumber: 42, - pullRequest: { head: { sha: "abc123" }, user: { login: "bot-user" } }, - }); + buffer.addComment({ path: "file.go", line: 10, body: "Inline comment" }); + buffer.setReviewMetadata("Fix this", "REQUEST_CHANGES"); + buffer.setReviewContext({ + repo: "owner/repo", + repoParts: { owner: "owner", repo: "repo" }, + pullRequestNumber: 42, + pullRequest: { head: { sha: "abc123" }, user: { login: "bot-user" } }, + }); - mockGithub.rest.pulls.createReview - // Initial attempt — own-PR 422 - .mockRejectedValueOnce(new Error("Can not request changes on your own pull request")) - // COMMENT retry with inline comments — line unresolvable - .mockRejectedValueOnce(new Error('Unprocessable Entity: "Line could not be resolved"')) - // Body-only COMMENT fallback — success - .mockResolvedValueOnce({ - data: { - id: 800, - html_url: "https://github.com/owner/repo/pull/42#pullrequestreview-800", - }, - }); + mockGithub.rest.pulls.createReview + // Initial attempt — own-PR 422 + .mockRejectedValueOnce(new Error("Can not request changes on your own pull request")) + // COMMENT retry with inline comments — line unresolvable + .mockRejectedValueOnce(new Error('Unprocessable Entity: "Line could not be resolved"')) + // Body-only COMMENT fallback — success + .mockResolvedValueOnce({ + data: { + id: 800, + html_url: "https://github.com/owner/repo/pull/42#pullrequestreview-800", + }, + }); - const result = await buffer.submitReview(); + const result = await buffer.submitReview(); - expect(result.success).toBe(true); - expect(result.event).toBe("COMMENT"); - expect(mockGithub.rest.pulls.createReview).toHaveBeenCalledTimes(3); - const bodyOnlyArgs = mockGithub.rest.pulls.createReview.mock.calls[2][0]; - expect(bodyOnlyArgs.event).toBe("COMMENT"); - expect(bodyOnlyArgs.comments).toBeUndefined(); - expect(bodyOnlyArgs.body).toContain("Inline comment"); - expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("COMMENT retry on own PR failed with unresolvable line(s)")); - } finally { - if (previousActor === undefined) { - delete process.env.GITHUB_ACTOR; - } else { - process.env.GITHUB_ACTOR = previousActor; - } - } + expect(result.success).toBe(true); + expect(result.event).toBe("COMMENT"); + expect(mockGithub.rest.pulls.createReview).toHaveBeenCalledTimes(3); + const bodyOnlyArgs = mockGithub.rest.pulls.createReview.mock.calls[2][0]; + expect(bodyOnlyArgs.event).toBe("COMMENT"); + expect(bodyOnlyArgs.comments).toBeUndefined(); + expect(bodyOnlyArgs.body).toContain("Inline comment"); + expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("COMMENT retry on own PR failed with unresolvable line(s)")); }); it("should skip (success:true, skipped:true) when PR is permanently locked after all retries", async () => { @@ -1473,48 +1453,38 @@ describe("pr_review_buffer (factory pattern)", () => { }); it("should retry body-only as COMMENT when body-only REQUEST_CHANGES is rejected on own PR after line-resolution failure", async () => { - const previousActor = process.env.GITHUB_ACTOR; - process.env.GITHUB_ACTOR = "someone-else"; - try { - buffer.addComment({ path: "file.go", line: 5, body: "Nil dereference on this line" }); - buffer.setReviewMetadata("Fix the blocking issues.", "REQUEST_CHANGES"); - buffer.setReviewContext({ - repo: "owner/repo", - repoParts: { owner: "owner", repo: "repo" }, - pullRequestNumber: 47375, - pullRequest: { head: { sha: "abc123" }, user: { login: "linter-miner[bot]" } }, - }); + buffer.addComment({ path: "file.go", line: 5, body: "Nil dereference on this line" }); + buffer.setReviewMetadata("Fix the blocking issues.", "REQUEST_CHANGES"); + buffer.setReviewContext({ + repo: "owner/repo", + repoParts: { owner: "owner", repo: "repo" }, + pullRequestNumber: 47375, + pullRequest: { head: { sha: "abc123" }, user: { login: "linter-miner[bot]" } }, + }); - mockGithub.rest.pulls.createReview - // Initial attempt — line resolution fails (this triggers the body-only path) - .mockRejectedValueOnce(new Error('Unprocessable Entity: "Line could not be resolved"')) - // Body-only REQUEST_CHANGES — own-PR 422 - .mockRejectedValueOnce(new Error("Can not request changes on your own pull request")) - // Body-only COMMENT retry — success - .mockResolvedValueOnce({ - data: { - id: 900, - html_url: "https://github.com/owner/repo/pull/47375#pullrequestreview-900", - }, - }); + mockGithub.rest.pulls.createReview + // Initial attempt — line resolution fails (this triggers the body-only path) + .mockRejectedValueOnce(new Error('Unprocessable Entity: "Line could not be resolved"')) + // Body-only REQUEST_CHANGES — own-PR 422 + .mockRejectedValueOnce(new Error("Can not request changes on your own pull request")) + // Body-only COMMENT retry — success + .mockResolvedValueOnce({ + data: { + id: 900, + html_url: "https://github.com/owner/repo/pull/47375#pullrequestreview-900", + }, + }); - const result = await buffer.submitReview(); + const result = await buffer.submitReview(); - expect(result.success).toBe(true); - expect(result.event).toBe("COMMENT"); - expect(mockGithub.rest.pulls.createReview).toHaveBeenCalledTimes(3); - const bodyOnlyCommentArgs = mockGithub.rest.pulls.createReview.mock.calls[2][0]; - expect(bodyOnlyCommentArgs.event).toBe("COMMENT"); - expect(bodyOnlyCommentArgs.comments).toBeUndefined(); - expect(bodyOnlyCommentArgs.body).toContain("Nil dereference on this line"); - expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("Body-only REQUEST_CHANGES review rejected on own PR")); - } finally { - if (previousActor === undefined) { - delete process.env.GITHUB_ACTOR; - } else { - process.env.GITHUB_ACTOR = previousActor; - } - } + expect(result.success).toBe(true); + expect(result.event).toBe("COMMENT"); + expect(mockGithub.rest.pulls.createReview).toHaveBeenCalledTimes(3); + const bodyOnlyCommentArgs = mockGithub.rest.pulls.createReview.mock.calls[2][0]; + expect(bodyOnlyCommentArgs.event).toBe("COMMENT"); + expect(bodyOnlyCommentArgs.comments).toBeUndefined(); + expect(bodyOnlyCommentArgs.body).toContain("Nil dereference on this line"); + expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("Body-only REQUEST_CHANGES review rejected on own PR")); }); it("should return failure when body-only COMMENT retry also fails on own PR", async () => {