From 0792a7dc300c0e6b8e524852492222665c64661e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 01:40:28 +0000 Subject: [PATCH 1/7] Initial plan From 15f02682f4635209ba320062b73ff597a2e596da Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 01:48:50 +0000 Subject: [PATCH 2/7] Handle non-fatal update-branch API responses in update_pull_request Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/update_pull_request.cjs | 18 ++++++++-- actions/setup/js/update_pull_request.test.cjs | 33 +++++++++++++++++++ .../reference/safe-outputs-pull-requests.md | 2 ++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/actions/setup/js/update_pull_request.cjs b/actions/setup/js/update_pull_request.cjs index 8d22e06f8ff..25e073299a3 100644 --- a/actions/setup/js/update_pull_request.cjs +++ b/actions/setup/js/update_pull_request.cjs @@ -18,6 +18,15 @@ const { generateHistoryUrl } = require("./generate_history_link.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); const { withRetry, isTransientError } = require("./error_recovery.cjs"); +/** + * @param {unknown} error + * @returns {boolean} + */ +function isNonFatalUpdateBranchError(error) { + const message = getErrorMessage(error).toLowerCase(); + return message.includes("there are no new commits on the base branch") || message.includes("merge conflict between base and head"); +} + /** * Execute the pull request update API call * @param {any} github - GitHub API client @@ -55,8 +64,13 @@ async function executePRUpdate(github, context, prNumber, updateData) { `update pull request #${prNumber} branch from base` ); } catch (error) { - core.warning(`Failed to update pull request #${prNumber} branch from base: ${getErrorMessage(error)}`); - throw error; + const errorMessage = getErrorMessage(error); + if (isNonFatalUpdateBranchError(error)) { + core.warning(`Failed to update pull request #${prNumber} branch from base (non-fatal): ${errorMessage}`); + } else { + core.warning(`Failed to update pull request #${prNumber} branch from base: ${errorMessage}`); + throw error; + } } } diff --git a/actions/setup/js/update_pull_request.test.cjs b/actions/setup/js/update_pull_request.test.cjs index e4a34d88f67..0980b9559a0 100644 --- a/actions/setup/js/update_pull_request.test.cjs +++ b/actions/setup/js/update_pull_request.test.cjs @@ -847,4 +847,37 @@ describe("update_pull_request.cjs - update_branch behavior", () => { expect(mockGithub.rest.pulls.updateBranch).toHaveBeenCalledTimes(1); expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("Failed to update pull request #100 branch from base")); }); + + it("should treat no-new-commits updateBranch response as a non-fatal no-op", async () => { + mockGithub.rest.pulls.updateBranch.mockRejectedValueOnce(new Error("There are no new commits on the base branch.")); + + const handler = await updatePRModule.main({ update_branch: true }); + const result = await handler({ pull_request_number: 100 }); + + expect(result.success).toBe(true); + expect(mockGithub.rest.pulls.updateBranch).toHaveBeenCalledTimes(1); + expect(mockGithub.rest.pulls.update).not.toHaveBeenCalled(); + expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("branch from base (non-fatal)")); + }); + + it("should continue title/body updates when updateBranch reports merge conflict", async () => { + mockGithub.rest.pulls.updateBranch.mockRejectedValueOnce(new Error("merge conflict between base and head")); + + const handler = await updatePRModule.main({}); + const result = await handler({ + pull_request_number: 100, + title: "Updated PR", + update_branch: true, + }); + + expect(result.success).toBe(true); + expect(mockGithub.rest.pulls.updateBranch).toHaveBeenCalledTimes(1); + expect(mockGithub.rest.pulls.update).toHaveBeenCalledWith({ + owner: "testowner", + repo: "testrepo", + pull_number: 100, + title: "Updated PR", + }); + expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("branch from base (non-fatal)")); + }); }); diff --git a/docs/src/content/docs/reference/safe-outputs-pull-requests.md b/docs/src/content/docs/reference/safe-outputs-pull-requests.md index 4802975a1b6..f07fcaee013 100644 --- a/docs/src/content/docs/reference/safe-outputs-pull-requests.md +++ b/docs/src/content/docs/reference/safe-outputs-pull-requests.md @@ -137,6 +137,8 @@ safe-outputs: When `update-branch: true` is set, the handler calls the GitHub REST `pulls.updateBranch` API to merge the latest base branch changes into the PR branch before applying title or body updates. This requires `contents: write` permission; without it only `contents: read` is needed. The field can also be used alone (with `title: false` and `body: false`) to update the branch without changing the PR description. +If GitHub reports `There are no new commits on the base branch.` or `merge conflict between base and head`, the branch update is treated as best-effort: the workflow logs a warning and continues processing the safe output. + When using `target: "*"`, the agent must provide `pull_request_number` in the output to identify which pull request to update. **Operation Types**: Same as `update-issue` (`append`, `prepend`, `replace`). Title updates always replace the existing title. Disable fields by setting to `false`. From 82b2e4e5011875a406bfd73bef92e36f95d0b7d6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 01:49:28 +0000 Subject: [PATCH 3/7] Narrow non-fatal update-branch matching to 422-class responses Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/update_pull_request.cjs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/actions/setup/js/update_pull_request.cjs b/actions/setup/js/update_pull_request.cjs index 25e073299a3..c9f1aedfdd6 100644 --- a/actions/setup/js/update_pull_request.cjs +++ b/actions/setup/js/update_pull_request.cjs @@ -23,8 +23,14 @@ const { withRetry, isTransientError } = require("./error_recovery.cjs"); * @returns {boolean} */ function isNonFatalUpdateBranchError(error) { + if (typeof error === "object" && error !== null && "status" in error && error.status !== 422) { + return false; + } + const message = getErrorMessage(error).toLowerCase(); - return message.includes("there are no new commits on the base branch") || message.includes("merge conflict between base and head"); + return ( + message.includes("there are no new commits on the base branch") || message.includes("merge conflict between base and head") + ); } /** From 4e4eb227ed7a358e4c39ab8ab21bb9a5bec1bd36 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 01:50:07 +0000 Subject: [PATCH 4/7] Refine soft-error matching and clarify merge-conflict continuation test Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/update_pull_request.cjs | 6 ++++-- actions/setup/js/update_pull_request.test.cjs | 3 +-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/actions/setup/js/update_pull_request.cjs b/actions/setup/js/update_pull_request.cjs index c9f1aedfdd6..a16e98f8d60 100644 --- a/actions/setup/js/update_pull_request.cjs +++ b/actions/setup/js/update_pull_request.cjs @@ -23,8 +23,10 @@ const { withRetry, isTransientError } = require("./error_recovery.cjs"); * @returns {boolean} */ function isNonFatalUpdateBranchError(error) { - if (typeof error === "object" && error !== null && "status" in error && error.status !== 422) { - return false; + if (typeof error === "object" && error !== null && "status" in error) { + if (error.status !== 422) { + return false; + } } const message = getErrorMessage(error).toLowerCase(); diff --git a/actions/setup/js/update_pull_request.test.cjs b/actions/setup/js/update_pull_request.test.cjs index 0980b9559a0..b33d7361847 100644 --- a/actions/setup/js/update_pull_request.test.cjs +++ b/actions/setup/js/update_pull_request.test.cjs @@ -863,11 +863,10 @@ describe("update_pull_request.cjs - update_branch behavior", () => { it("should continue title/body updates when updateBranch reports merge conflict", async () => { mockGithub.rest.pulls.updateBranch.mockRejectedValueOnce(new Error("merge conflict between base and head")); - const handler = await updatePRModule.main({}); + const handler = await updatePRModule.main({ update_branch: true }); const result = await handler({ pull_request_number: 100, title: "Updated PR", - update_branch: true, }); expect(result.success).toBe(true); From c28696776ed66438d460833b928f7844c2461cf5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 01:50:53 +0000 Subject: [PATCH 5/7] Document non-fatal update-branch 422 message handling Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/update_pull_request.cjs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/actions/setup/js/update_pull_request.cjs b/actions/setup/js/update_pull_request.cjs index a16e98f8d60..b3cbfb51aff 100644 --- a/actions/setup/js/update_pull_request.cjs +++ b/actions/setup/js/update_pull_request.cjs @@ -23,12 +23,15 @@ const { withRetry, isTransientError } = require("./error_recovery.cjs"); * @returns {boolean} */ function isNonFatalUpdateBranchError(error) { - if (typeof error === "object" && error !== null && "status" in error) { - if (error.status !== 422) { - return false; - } + const hasStatus = typeof error === "object" && error !== null && "status" in error; + if (hasStatus && error.status !== 422) { + return false; } + // GitHub update-branch API can return these 422 messages for benign conditions: + // - already up to date ("There are no new commits on the base branch") + // - cannot auto-update due to conflict ("merge conflict between base and head") + // These should not fail safe output processing. const message = getErrorMessage(error).toLowerCase(); return ( message.includes("there are no new commits on the base branch") || message.includes("merge conflict between base and head") From 9c7cde46ee5de121d252856393cdd49644ababc9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 02:25:32 +0000 Subject: [PATCH 6/7] Fix checkJs-safe status narrowing in non-fatal update-branch classifier Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/update_pull_request.cjs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/actions/setup/js/update_pull_request.cjs b/actions/setup/js/update_pull_request.cjs index b3cbfb51aff..76c4c69869a 100644 --- a/actions/setup/js/update_pull_request.cjs +++ b/actions/setup/js/update_pull_request.cjs @@ -23,8 +23,10 @@ const { withRetry, isTransientError } = require("./error_recovery.cjs"); * @returns {boolean} */ function isNonFatalUpdateBranchError(error) { - const hasStatus = typeof error === "object" && error !== null && "status" in error; - if (hasStatus && error.status !== 422) { + /** @type {number | undefined} */ + const status = + typeof error === "object" && error !== null && "status" in error ? /** @type {{status?: number}} */ (error).status : undefined; + if (status !== undefined && status !== 422) { return false; } From 36c49144dd971ed3ebf6e6b78012014353f760fc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 02:26:11 +0000 Subject: [PATCH 7/7] Refactor non-fatal update-branch status guard for readability Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/update_pull_request.cjs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/actions/setup/js/update_pull_request.cjs b/actions/setup/js/update_pull_request.cjs index 76c4c69869a..59e72cf3e85 100644 --- a/actions/setup/js/update_pull_request.cjs +++ b/actions/setup/js/update_pull_request.cjs @@ -24,8 +24,10 @@ const { withRetry, isTransientError } = require("./error_recovery.cjs"); */ function isNonFatalUpdateBranchError(error) { /** @type {number | undefined} */ - const status = - typeof error === "object" && error !== null && "status" in error ? /** @type {{status?: number}} */ (error).status : undefined; + let status; + if (typeof error === "object" && error !== null && "status" in error) { + status = /** @type {{status?: number}} */ (error).status; + } if (status !== undefined && status !== 422) { return false; }