diff --git a/actions/setup/js/update_pull_request.cjs b/actions/setup/js/update_pull_request.cjs index a80344b526d..dcdca3b020e 100644 --- a/actions/setup/js/update_pull_request.cjs +++ b/actions/setup/js/update_pull_request.cjs @@ -38,6 +38,7 @@ function isNonFatalUpdateBranchError(error) { // Require both permission wording and update-branch context to avoid treating unrelated // "workflows permission" errors as non-fatal for pull request branch updates. const hasWorkflowsPermissionError = hasWorkflowsPermissionPhrase && (hasWorkflowMutationRefusal || message.includes("update pull request")); + const hasVerifiedSignaturesRuleViolation = message.includes("repository rule violations found") && /commits?\s+must\s+have\s+verified\s+signatures\b/i.test(message); if (status !== undefined) { if (status === 403 && hasWorkflowsPermissionError) { @@ -51,8 +52,10 @@ function isNonFatalUpdateBranchError(error) { // 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") + // - branch protection/ruleset refusal when the synthetic merge commit would be unsigned + // ("Repository rule violations found" + "Commits must have verified signatures") // These should not fail safe output processing. - return message.includes("there are no new commits on the base branch") || message.includes("merge conflict between base and head") || hasWorkflowsPermissionError; + return message.includes("there are no new commits on the base branch") || message.includes("merge conflict between base and head") || hasWorkflowsPermissionError || hasVerifiedSignaturesRuleViolation; } /** diff --git a/actions/setup/js/update_pull_request.test.cjs b/actions/setup/js/update_pull_request.test.cjs index 6fb5e16ae11..7f2ad8f7cd8 100644 --- a/actions/setup/js/update_pull_request.test.cjs +++ b/actions/setup/js/update_pull_request.test.cjs @@ -941,4 +941,40 @@ describe("update_pull_request.cjs - update_branch behavior", () => { }); expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("branch from base (non-fatal)")); }); + + it("should treat verified-signatures ruleset rejection as non-fatal for update-branch-only requests", async () => { + const ruleViolationError = new Error("Repository rule violations found\n\nCommits must have verified signatures."); + ruleViolationError.status = 422; + mockGithub.rest.pulls.updateBranch.mockRejectedValueOnce(ruleViolationError); + + 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 hits verified-signatures ruleset rejection", async () => { + const ruleViolationError = new Error("Repository rule violations found\n\nCommits must have verified signatures."); + ruleViolationError.status = 422; + mockGithub.rest.pulls.updateBranch.mockRejectedValueOnce(ruleViolationError); + + const handler = await updatePRModule.main({ update_branch: true }); + const result = await handler({ + pull_request_number: 100, + title: "Updated PR", + }); + + 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)")); + }); });