From 1c7a623a893b086a73bb58dc79da5c6f6deec18c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Aug 2026 05:18:12 +0000 Subject: [PATCH 1/8] Initial plan From b259fe7e3e9bd3b4ef7050bbe7a5bff389e04c5d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Aug 2026 05:25:03 +0000 Subject: [PATCH 2/8] Handle stacked PR branch-update errors as non-fatal Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/update_pull_request.cjs | 5 +++++ actions/setup/js/update_pull_request.test.cjs | 22 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/actions/setup/js/update_pull_request.cjs b/actions/setup/js/update_pull_request.cjs index 7977e1e8e07..3cc0fad8e21 100644 --- a/actions/setup/js/update_pull_request.cjs +++ b/actions/setup/js/update_pull_request.cjs @@ -45,6 +45,11 @@ function isNonFatalUpdateBranchError(error) { // GitHub update-branch API also returns 403 with this message when a PR contains workflow // file changes and the check times out, rather than the usual "refusing to allow" phrase. const hasWorkflowsScopeRequired = message.includes("`workflows` scope may be required") || message.includes("unable to determine if workflow can be created or updated"); + const hasStackedPRUnsupportedError = message.includes("updating a stacked pr's branch via this endpoint is not supported"); + + if (hasStackedPRUnsupportedError) { + return true; + } if (status !== undefined) { if (status === 403 && (hasWorkflowsPermissionError || hasWorkflowsScopeRequired)) { diff --git a/actions/setup/js/update_pull_request.test.cjs b/actions/setup/js/update_pull_request.test.cjs index 57e57229661..35d82f183ee 100644 --- a/actions/setup/js/update_pull_request.test.cjs +++ b/actions/setup/js/update_pull_request.test.cjs @@ -1009,6 +1009,28 @@ describe("update_pull_request.cjs - update_branch behavior", () => { expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("branch from base (non-fatal)")); }); + it("should continue title/body updates when updateBranch reports stacked-PR unsupported", async () => { + const stackedPRError = new Error("Updating a stacked PR's branch via this endpoint is not supported."); + stackedPRError.status = 422; + mockGithub.rest.pulls.updateBranch.mockRejectedValueOnce(stackedPRError); + + 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)")); + }); + it("should continue title/body updates when updateBranch gets workflows-scope-required 403 (scope phrase variant)", async () => { // Message matches only the "`workflows` scope may be required" branch of hasWorkflowsScopeRequired. const scopeError = new Error("Validation failed; `workflows` scope may be required due to timeout in check."); From 96f593ca2611eecbcd0e65281006a161f49bd399 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Aug 2026 11:27:05 +0000 Subject: [PATCH 3/8] Gate stacked PR update-branch non-fatal handling on 422 status Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/update_pull_request.cjs | 2 +- actions/setup/js/update_pull_request.test.cjs | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/actions/setup/js/update_pull_request.cjs b/actions/setup/js/update_pull_request.cjs index 3cc0fad8e21..cd3705492a4 100644 --- a/actions/setup/js/update_pull_request.cjs +++ b/actions/setup/js/update_pull_request.cjs @@ -47,7 +47,7 @@ function isNonFatalUpdateBranchError(error) { const hasWorkflowsScopeRequired = message.includes("`workflows` scope may be required") || message.includes("unable to determine if workflow can be created or updated"); const hasStackedPRUnsupportedError = message.includes("updating a stacked pr's branch via this endpoint is not supported"); - if (hasStackedPRUnsupportedError) { + if (status === 422 && hasStackedPRUnsupportedError) { return true; } diff --git a/actions/setup/js/update_pull_request.test.cjs b/actions/setup/js/update_pull_request.test.cjs index 35d82f183ee..7672cf6a124 100644 --- a/actions/setup/js/update_pull_request.test.cjs +++ b/actions/setup/js/update_pull_request.test.cjs @@ -1031,6 +1031,23 @@ describe("update_pull_request.cjs - update_branch behavior", () => { expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("branch from base (non-fatal)")); }); + it("should keep stacked-PR unsupported fatal when updateBranch status is not 422", async () => { + const stackedPRError = new Error("Updating a stacked PR's branch via this endpoint is not supported."); + stackedPRError.status = 403; + mockGithub.rest.pulls.updateBranch.mockRejectedValueOnce(stackedPRError); + + const handler = await updatePRModule.main({ update_branch: true }); + const result = await handler({ + pull_request_number: 100, + title: "Updated PR", + }); + + expect(result.success).toBe(false); + expect(result.error).toContain("update pull request #100 branch from base failed"); + expect(mockGithub.rest.pulls.update).not.toHaveBeenCalled(); + expect(mockCore.warning).toHaveBeenCalledWith(expect.not.stringContaining("(non-fatal)")); + }); + it("should continue title/body updates when updateBranch gets workflows-scope-required 403 (scope phrase variant)", async () => { // Message matches only the "`workflows` scope may be required" branch of hasWorkflowsScopeRequired. const scopeError = new Error("Validation failed; `workflows` scope may be required due to timeout in check."); From b494d67d00e809317df14128f4c2ecc73e156212 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Aug 2026 11:59:17 +0000 Subject: [PATCH 4/8] Try stacked PR stack-sync fallback for update_branch failures Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/update_pull_request.cjs | 93 +++++++++++++++++-- actions/setup/js/update_pull_request.test.cjs | 78 ++++++++++++++++ 2 files changed, 165 insertions(+), 6 deletions(-) diff --git a/actions/setup/js/update_pull_request.cjs b/actions/setup/js/update_pull_request.cjs index cd3705492a4..d38c167b78d 100644 --- a/actions/setup/js/update_pull_request.cjs +++ b/actions/setup/js/update_pull_request.cjs @@ -19,9 +19,9 @@ const { withRetry, isTransientError } = require("./error_recovery.cjs"); /** * @param {unknown} error - * @returns {boolean} + * @returns {number|undefined} */ -function isNonFatalUpdateBranchError(error) { +function getErrorStatus(error) { // Resolve the effective HTTP status by checking the error and its .originalError chain. // withRetry wraps the original error in an enhanced error that lacks .status, so we need // to walk the chain to find the underlying status from the GitHub API response. @@ -36,6 +36,25 @@ function isNonFatalUpdateBranchError(error) { } current = current.originalError ?? null; } + return status; +} + +/** + * @param {unknown} error + * @returns {boolean} + */ +function isStackedPRUnsupportedUpdateBranchError(error) { + const status = getErrorStatus(error); + const message = getErrorMessage(error).toLowerCase(); + return status === 422 && message.includes("updating a stacked pr's branch via this endpoint is not supported"); +} + +/** + * @param {unknown} error + * @returns {boolean} + */ +function isNonFatalUpdateBranchError(error) { + const status = getErrorStatus(error); const message = getErrorMessage(error).toLowerCase(); const hasWorkflowsPermissionPhrase = /without\s+`?workflows`?\s+permission/i.test(message); const hasWorkflowMutationRefusal = message.includes("refusing to allow a github app to create or update workflow"); @@ -45,9 +64,7 @@ function isNonFatalUpdateBranchError(error) { // GitHub update-branch API also returns 403 with this message when a PR contains workflow // file changes and the check times out, rather than the usual "refusing to allow" phrase. const hasWorkflowsScopeRequired = message.includes("`workflows` scope may be required") || message.includes("unable to determine if workflow can be created or updated"); - const hasStackedPRUnsupportedError = message.includes("updating a stacked pr's branch via this endpoint is not supported"); - - if (status === 422 && hasStackedPRUnsupportedError) { + if (isStackedPRUnsupportedUpdateBranchError(error)) { return true; } @@ -78,6 +95,63 @@ function isNonFatalUpdateBranchError(error) { ); } +/** + * @param {any} github + * @param {any} context + * @param {number} prNumber + * @returns {Promise} + */ +async function tryStackedPRUpdateBranch(github, context, prNumber) { + /** @type {number|undefined} */ + let stackNumber; + + try { + const { data: pullRequest } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber, + }); + if (typeof pullRequest?.stack?.number === "number") { + stackNumber = pullRequest.stack.number; + } + } catch { + // Ignore metadata fetch failures; we can still attempt list lookup below. + } + + if (stackNumber === undefined) { + try { + const { data: stacks } = await github.request("GET /repos/{owner}/{repo}/stacks", { + owner: context.repo.owner, + repo: context.repo.repo, + pull_request: prNumber, + per_page: 1, + }); + if (Array.isArray(stacks) && stacks.length > 0 && typeof stacks[0]?.number === "number") { + stackNumber = stacks[0].number; + } + } catch { + return false; + } + } + + if (stackNumber === undefined) { + return false; + } + + try { + await github.request("POST /repos/{owner}/{repo}/stacks/{stack_number}/sync", { + owner: context.repo.owner, + repo: context.repo.repo, + stack_number: stackNumber, + }); + core.info(`Synced stacked PR #${prNumber} via stack #${stackNumber}`); + return true; + } catch (error) { + core.info(`Unable to sync stacked PR #${prNumber} via stack #${stackNumber}: ${getErrorMessage(error)}`); + return false; + } +} + /** * Execute the pull request update API call * @param {any} github - GitHub API client @@ -116,7 +190,14 @@ async function executePRUpdate(github, context, prNumber, updateData) { ); } catch (error) { const errorMessage = getErrorMessage(error); - if (isNonFatalUpdateBranchError(error)) { + if (isStackedPRUnsupportedUpdateBranchError(error)) { + const didSyncStack = await tryStackedPRUpdateBranch(github, context, prNumber); + if (didSyncStack) { + core.info(`Updated stacked PR #${prNumber} branch via stack sync`); + } else { + core.warning(`Failed to update pull request #${prNumber} branch from base (non-fatal): ${errorMessage}`); + } + } else 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}`); diff --git a/actions/setup/js/update_pull_request.test.cjs b/actions/setup/js/update_pull_request.test.cjs index 7672cf6a124..7e4d9479bba 100644 --- a/actions/setup/js/update_pull_request.test.cjs +++ b/actions/setup/js/update_pull_request.test.cjs @@ -29,6 +29,7 @@ const mockGithub = { updateBranch: vi.fn(), }, }, + request: vi.fn(), }; const mockContext = { @@ -101,6 +102,12 @@ describe("update_pull_request.cjs - executePRUpdate function", () => { message: "Branch updated", }, }); + mockGithub.request.mockImplementation(async route => { + if (route === "GET /repos/{owner}/{repo}/stacks") { + return { data: [] }; + } + throw new Error(`Unexpected route: ${route}`); + }); }); describe("Replace operation", () => { @@ -1031,6 +1038,77 @@ describe("update_pull_request.cjs - update_branch behavior", () => { expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("branch from base (non-fatal)")); }); + it("should sync stack when updateBranch reports stacked-PR unsupported and stack sync API is available", async () => { + const stackedPRError = new Error("Updating a stacked PR's branch via this endpoint is not supported."); + stackedPRError.status = 422; + mockGithub.rest.pulls.updateBranch.mockRejectedValueOnce(stackedPRError); + mockGithub.request.mockImplementation(async route => { + if (route === "GET /repos/{owner}/{repo}/stacks") { + return { + data: [{ number: 7 }], + }; + } + if (route === "POST /repos/{owner}/{repo}/stacks/{stack_number}/sync") { + return { data: {} }; + } + throw new Error(`Unexpected route: ${route}`); + }); + + 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.request).toHaveBeenCalledWith("POST /repos/{owner}/{repo}/stacks/{stack_number}/sync", { + owner: "testowner", + repo: "testrepo", + stack_number: 7, + }); + expect(mockGithub.rest.pulls.update).toHaveBeenCalledWith({ + owner: "testowner", + repo: "testrepo", + pull_number: 100, + title: "Updated PR", + }); + expect(mockCore.warning).not.toHaveBeenCalledWith(expect.stringContaining("branch from base (non-fatal)")); + }); + + it("should keep stacked-PR unsupported non-fatal when stack sync API attempt fails", async () => { + const stackedPRError = new Error("Updating a stacked PR's branch via this endpoint is not supported."); + stackedPRError.status = 422; + mockGithub.rest.pulls.updateBranch.mockRejectedValueOnce(stackedPRError); + mockGithub.request.mockImplementation(async route => { + if (route === "GET /repos/{owner}/{repo}/stacks") { + return { + data: [{ number: 7 }], + }; + } + if (route === "POST /repos/{owner}/{repo}/stacks/{stack_number}/sync") { + throw new Error("stack sync endpoint unavailable"); + } + throw new Error(`Unexpected route: ${route}`); + }); + + 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)")); + }); + it("should keep stacked-PR unsupported fatal when updateBranch status is not 422", async () => { const stackedPRError = new Error("Updating a stacked PR's branch via this endpoint is not supported."); stackedPRError.status = 403; From 2ed52eeab3b4e92c556fbf93896d7de31a5282ce Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Aug 2026 12:10:24 +0000 Subject: [PATCH 5/8] Add explicit reason logging for update-branch return paths Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- actions/setup/js/update_pull_request.cjs | 19 +++++++++++++------ actions/setup/js/update_pull_request.test.cjs | 1 + 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/actions/setup/js/update_pull_request.cjs b/actions/setup/js/update_pull_request.cjs index d38c167b78d..2b401b68578 100644 --- a/actions/setup/js/update_pull_request.cjs +++ b/actions/setup/js/update_pull_request.cjs @@ -65,14 +65,17 @@ function isNonFatalUpdateBranchError(error) { // file changes and the check times out, rather than the usual "refusing to allow" phrase. const hasWorkflowsScopeRequired = message.includes("`workflows` scope may be required") || message.includes("unable to determine if workflow can be created or updated"); if (isStackedPRUnsupportedUpdateBranchError(error)) { + core.info(`Treating update-branch error as non-fatal: stacked PR branch update endpoint unsupported (status=${status ?? "unknown"})`); return true; } if (status !== undefined) { if (status === 403 && (hasWorkflowsPermissionError || hasWorkflowsScopeRequired)) { + core.info(`Treating update-branch error as non-fatal: workflows permission/scope restriction on 403 (status=${status})`); return true; } if (status !== 422 && !message.includes("head ref does not exist")) { + core.info(`Treating update-branch error as fatal: status is not 422 and head-ref is not missing (status=${status})`); return false; } } @@ -88,11 +91,13 @@ function isNonFatalUpdateBranchError(error) { // errors. hasWorkflowsPermissionError / hasWorkflowsScopeRequired are only checked for errors // with no numeric status (status === undefined); the explicit 403 case is already handled by // the if-block above. - return ( + const isNonFatal = (status !== undefined && message.includes("head ref does not exist")) || (status === 422 && (message.includes("there are no new commits on the base branch") || message.includes("merge conflict between base and head"))) || - ((hasWorkflowsPermissionError || hasWorkflowsScopeRequired) && status === undefined) - ); + ((hasWorkflowsPermissionError || hasWorkflowsScopeRequired) && status === undefined); + const finalReason = isNonFatal ? "matched known benign update-branch validation/scope condition" : "did not match any known benign update-branch condition"; + core.info(`Treating update-branch error as ${isNonFatal ? "non-fatal" : "fatal"}: ${finalReason} (status=${status ?? "unknown"})`); + return isNonFatal; } /** @@ -114,8 +119,8 @@ async function tryStackedPRUpdateBranch(github, context, prNumber) { if (typeof pullRequest?.stack?.number === "number") { stackNumber = pullRequest.stack.number; } - } catch { - // Ignore metadata fetch failures; we can still attempt list lookup below. + } catch (error) { + core.info(`Unable to resolve stack number from pull metadata for #${prNumber}: ${getErrorMessage(error)}; trying stack list lookup`); } if (stackNumber === undefined) { @@ -129,12 +134,14 @@ async function tryStackedPRUpdateBranch(github, context, prNumber) { if (Array.isArray(stacks) && stacks.length > 0 && typeof stacks[0]?.number === "number") { stackNumber = stacks[0].number; } - } catch { + } catch (error) { + core.info(`Unable to resolve stack number from stack list for #${prNumber}: ${getErrorMessage(error)}; skipping stack sync`); return false; } } if (stackNumber === undefined) { + core.info(`Unable to sync stacked PR #${prNumber}: no stack number could be resolved`); return false; } diff --git a/actions/setup/js/update_pull_request.test.cjs b/actions/setup/js/update_pull_request.test.cjs index 7e4d9479bba..5f6d21fe1cb 100644 --- a/actions/setup/js/update_pull_request.test.cjs +++ b/actions/setup/js/update_pull_request.test.cjs @@ -1123,6 +1123,7 @@ describe("update_pull_request.cjs - update_branch behavior", () => { expect(result.success).toBe(false); expect(result.error).toContain("update pull request #100 branch from base failed"); expect(mockGithub.rest.pulls.update).not.toHaveBeenCalled(); + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Treating update-branch error as fatal: status is not 422 and head-ref is not missing")); expect(mockCore.warning).toHaveBeenCalledWith(expect.not.stringContaining("(non-fatal)")); }); From 97e7f8beebc1630c196e208f9fd886f2cf5b5e54 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Aug 2026 12:29:34 +0000 Subject: [PATCH 6/8] Add update-branch.stacks toggle and stacked PR debug logging Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/changeset.lock.yml | 4 +- .../workflows/pr-description-caveman.lock.yml | 4 +- .github/workflows/pr-sous-chef.lock.yml | 4 +- .github/workflows/smoke-ci.lock.yml | 4 +- .github/workflows/smoke-claude.lock.yml | 4 +- actions/setup/js/update_pull_request.cjs | 19 ++++++++-- actions/setup/js/update_pull_request.test.cjs | 38 +++++++++++++++++++ pkg/parser/schemas/main_workflow_schema.json | 4 ++ .../compiler_safe_outputs_config_test.go | 33 ++++++++++------ pkg/workflow/safe_outputs_handler_registry.go | 1 + pkg/workflow/update_pull_request.go | 12 +++--- pkg/workflow/update_pull_request_test.go | 9 +++++ 12 files changed, 106 insertions(+), 30 deletions(-) diff --git a/.github/workflows/changeset.lock.yml b/.github/workflows/changeset.lock.yml index 78ff39544cb..33752eda48e 100644 --- a/.github/workflows/changeset.lock.yml +++ b/.github/workflows/changeset.lock.yml @@ -584,7 +584,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_to_pull_request_branch\":{\"allowed_files\":[\".changeset/*.md\"],\"commit_title_suffix\":\" [skip-ci]\",\"if_no_changes\":\"warn\",\"max_patch_size\":4096,\"patch_format\":\"bundle\",\"protect_top_level_dot_folders\":true,\"protected_dot_folder_excludes\":[\".changeset/\"],\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\"],\"protected_files_policy\":\"blocked\"},\"report_incomplete\":{},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":false,\"default_operation\":\"append\",\"max\":1,\"update_branch\":false}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_to_pull_request_branch\":{\"allowed_files\":[\".changeset/*.md\"],\"commit_title_suffix\":\" [skip-ci]\",\"if_no_changes\":\"warn\",\"max_patch_size\":4096,\"patch_format\":\"bundle\",\"protect_top_level_dot_folders\":true,\"protected_dot_folder_excludes\":[\".changeset/\"],\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\"],\"protected_files_policy\":\"blocked\"},\"report_incomplete\":{},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":false,\"default_operation\":\"append\",\"max\":1,\"update_branch\":false,\"update_branch_stacks\":true}}" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -2206,7 +2206,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.grafana.net,*.sentry.io,172.30.0.1,api.github.com,api.npms.io,api.openai.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,bun.sh,cdn.jsdelivr.net,chatgpt.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,deb.nodesource.com,deno.land,docs.github.com,esm.sh,get.pnpm.io,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,go.dev,golang.org,googleapis.deno.dev,googlechromelabs.github.io,goproxy.io,host.docker.internal,json-schema.org,json.schemastore.org,jsr.io,keyserver.ubuntu.com,lfs.github.com,nodejs.org,npm.pkg.github.com,npmjs.com,npmjs.org,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,openai.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,patchdiff.githubusercontent.com,pkg.go.dev,ppa.launchpad.net,proxy.golang.org,raw.githubusercontent.com,registry.bower.io,registry.npmjs.com,registry.npmjs.org,registry.yarnpkg.com,repo.yarnpkg.com,s.symcb.com,s.symcd.com,security.ubuntu.com,skimdb.npmjs.com,storage.googleapis.com,sum.golang.org,telemetry.vercel.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.npmjs.com,www.npmjs.org,yarnpkg.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_to_pull_request_branch\":{\"allowed_files\":[\".changeset/*.md\"],\"commit_title_suffix\":\" [skip-ci]\",\"if_no_changes\":\"warn\",\"max_patch_size\":4096,\"patch_format\":\"bundle\",\"protect_top_level_dot_folders\":true,\"protected_dot_folder_excludes\":[\".changeset/\"],\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\"],\"protected_files_policy\":\"blocked\"},\"report_incomplete\":{},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":false,\"default_operation\":\"append\",\"max\":1,\"update_branch\":false}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_to_pull_request_branch\":{\"allowed_files\":[\".changeset/*.md\"],\"commit_title_suffix\":\" [skip-ci]\",\"if_no_changes\":\"warn\",\"max_patch_size\":4096,\"patch_format\":\"bundle\",\"protect_top_level_dot_folders\":true,\"protected_dot_folder_excludes\":[\".changeset/\"],\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\"],\"protected_files_policy\":\"blocked\"},\"report_incomplete\":{},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":false,\"default_operation\":\"append\",\"max\":1,\"update_branch\":false,\"update_branch_stacks\":true}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pr-description-caveman.lock.yml b/.github/workflows/pr-description-caveman.lock.yml index d31fe225072..8e682ef70e6 100644 --- a/.github/workflows/pr-description-caveman.lock.yml +++ b/.github/workflows/pr-description-caveman.lock.yml @@ -533,7 +533,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":false,\"default_operation\":\"replace\",\"max\":1,\"update_branch\":false}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":false,\"default_operation\":\"replace\",\"max\":1,\"update_branch\":false,\"update_branch_stacks\":true}}" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -1723,7 +1723,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":false,\"default_operation\":\"replace\",\"max\":1,\"update_branch\":false}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":false,\"default_operation\":\"replace\",\"max\":1,\"update_branch\":false,\"update_branch_stacks\":true}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/pr-sous-chef.lock.yml b/.github/workflows/pr-sous-chef.lock.yml index 7056d7783a7..4fe3b6dc648 100644 --- a/.github/workflows/pr-sous-chef.lock.yml +++ b/.github/workflows/pr-sous-chef.lock.yml @@ -825,7 +825,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"github-token\":\"${GH_AW_SECRET_AWI_MAINTENANCE_TOKEN}\",\"max\":4,\"target\":\"*\"},\"create_issue\":{\"close_older_issues\":true,\"expires\":72,\"group_by_day\":true,\"labels\":[\"automation\"],\"max\":1,\"title_prefix\":\"[pr-sous-chef] \"},\"create_report_incomplete_issue\":{},\"dismiss_pull_request_review\":{\"max\":20,\"target\":\"*\"},\"mentions\":{\"allowed\":[\"copilot\"]},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_to_pull_request_branch\":{\"commit_title_suffix\":\" [pr-sous-chef]\",\"excluded_files\":[\".github/workflows/**\"],\"if_no_changes\":\"ignore\",\"max\":10,\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"PI.md\",\"AGENTS.md\"],\"target\":\"*\"},\"report_incomplete\":{},\"resolve_pull_request_review_thread\":{\"max\":40},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":false,\"default_operation\":\"append\",\"max\":10,\"target\":\"*\",\"update_branch\":true}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"github-token\":\"${GH_AW_SECRET_AWI_MAINTENANCE_TOKEN}\",\"max\":4,\"target\":\"*\"},\"create_issue\":{\"close_older_issues\":true,\"expires\":72,\"group_by_day\":true,\"labels\":[\"automation\"],\"max\":1,\"title_prefix\":\"[pr-sous-chef] \"},\"create_report_incomplete_issue\":{},\"dismiss_pull_request_review\":{\"max\":20,\"target\":\"*\"},\"mentions\":{\"allowed\":[\"copilot\"]},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_to_pull_request_branch\":{\"commit_title_suffix\":\" [pr-sous-chef]\",\"excluded_files\":[\".github/workflows/**\"],\"if_no_changes\":\"ignore\",\"max\":10,\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"PI.md\",\"AGENTS.md\"],\"target\":\"*\"},\"report_incomplete\":{},\"resolve_pull_request_review_thread\":{\"max\":40},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":false,\"default_operation\":\"append\",\"max\":10,\"target\":\"*\",\"update_branch\":true,\"update_branch_stacks\":true}}" GH_AW_SECRET_AWI_MAINTENANCE_TOKEN: ${{ secrets.AWI_MAINTENANCE_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} GH_AW_SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.AWI_MAINTENANCE_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} GH_AW_SECRET_GITHUB_TOKEN: ${{ secrets.AWI_MAINTENANCE_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -2494,7 +2494,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.grafana.net,*.sentry.io,api.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,go.dev,golang.org,goproxy.io,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkg.go.dev,ppa.launchpad.net,proxy.golang.org,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,storage.googleapis.com,sum.golang.org,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"github-token\":\"${{ secrets.AWI_MAINTENANCE_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}\",\"max\":4,\"target\":\"*\"},\"create_issue\":{\"close_older_issues\":true,\"expires\":72,\"group_by_day\":true,\"labels\":[\"automation\"],\"max\":1,\"title_prefix\":\"[pr-sous-chef] \"},\"create_report_incomplete_issue\":{},\"dismiss_pull_request_review\":{\"max\":20,\"target\":\"*\"},\"mentions\":{\"allowed\":[\"copilot\"]},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_to_pull_request_branch\":{\"commit_title_suffix\":\" [pr-sous-chef]\",\"excluded_files\":[\".github/workflows/**\"],\"if_no_changes\":\"ignore\",\"max\":10,\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"PI.md\",\"AGENTS.md\"],\"target\":\"*\"},\"report_incomplete\":{},\"resolve_pull_request_review_thread\":{\"max\":40},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":false,\"default_operation\":\"append\",\"max\":10,\"target\":\"*\",\"update_branch\":true}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"github-token\":\"${{ secrets.AWI_MAINTENANCE_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}\",\"max\":4,\"target\":\"*\"},\"create_issue\":{\"close_older_issues\":true,\"expires\":72,\"group_by_day\":true,\"labels\":[\"automation\"],\"max\":1,\"title_prefix\":\"[pr-sous-chef] \"},\"create_report_incomplete_issue\":{},\"dismiss_pull_request_review\":{\"max\":20,\"target\":\"*\"},\"mentions\":{\"allowed\":[\"copilot\"]},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_to_pull_request_branch\":{\"commit_title_suffix\":\" [pr-sous-chef]\",\"excluded_files\":[\".github/workflows/**\"],\"if_no_changes\":\"ignore\",\"max\":10,\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"PI.md\",\"AGENTS.md\"],\"target\":\"*\"},\"report_incomplete\":{},\"resolve_pull_request_review_thread\":{\"max\":40},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":false,\"default_operation\":\"append\",\"max\":10,\"target\":\"*\",\"update_branch\":true,\"update_branch_stacks\":true}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/smoke-ci.lock.yml b/.github/workflows/smoke-ci.lock.yml index ae43db4f0ea..b65ea9ce2a5 100644 --- a/.github/workflows/smoke-ci.lock.yml +++ b/.github/workflows/smoke-ci.lock.yml @@ -609,7 +609,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1},\"add_labels\":{\"allowed\":[\"ai-generated\"],\"max\":1},\"comment_memory\":{\"max\":1,\"memory_id\":\"default\"},\"create_issue\":{\"close_older_issues\":true,\"close_older_key\":\"smoke-ci-memory-safe-outputs\",\"labels\":[\"ai-generated\"],\"max\":1,\"title_prefix\":\"[smoke-ci] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":10240}]},\"remove_labels\":{\"allowed\":[\"ai-generated\"],\"max\":1},\"report_incomplete\":{},\"update_issue\":{\"allow_body\":true,\"max\":1,\"target\":\"*\"},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":true,\"max\":1,\"target\":\"*\",\"update_branch\":false}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1},\"add_labels\":{\"allowed\":[\"ai-generated\"],\"max\":1},\"comment_memory\":{\"max\":1,\"memory_id\":\"default\"},\"create_issue\":{\"close_older_issues\":true,\"close_older_key\":\"smoke-ci-memory-safe-outputs\",\"labels\":[\"ai-generated\"],\"max\":1,\"title_prefix\":\"[smoke-ci] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":10240}]},\"remove_labels\":{\"allowed\":[\"ai-generated\"],\"max\":1},\"report_incomplete\":{},\"update_issue\":{\"allow_body\":true,\"max\":1,\"target\":\"*\"},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":true,\"max\":1,\"target\":\"*\",\"update_branch\":false,\"update_branch_stacks\":true}}" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -1848,7 +1848,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.grafana.net,*.sentry.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1},\"add_labels\":{\"allowed\":[\"ai-generated\"],\"max\":1},\"comment_memory\":{\"max\":1,\"memory_id\":\"default\"},\"create_issue\":{\"close_older_issues\":true,\"close_older_key\":\"smoke-ci-memory-safe-outputs\",\"labels\":[\"ai-generated\"],\"max\":1,\"title_prefix\":\"[smoke-ci] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"remove_labels\":{\"allowed\":[\"ai-generated\"],\"max\":1},\"report_incomplete\":{},\"update_issue\":{\"allow_body\":true,\"max\":1,\"target\":\"*\"},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":true,\"max\":1,\"target\":\"*\",\"update_branch\":false}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1},\"add_labels\":{\"allowed\":[\"ai-generated\"],\"max\":1},\"comment_memory\":{\"max\":1,\"memory_id\":\"default\"},\"create_issue\":{\"close_older_issues\":true,\"close_older_key\":\"smoke-ci-memory-safe-outputs\",\"labels\":[\"ai-generated\"],\"max\":1,\"title_prefix\":\"[smoke-ci] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"remove_labels\":{\"allowed\":[\"ai-generated\"],\"max\":1},\"report_incomplete\":{},\"update_issue\":{\"allow_body\":true,\"max\":1,\"target\":\"*\"},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":true,\"max\":1,\"target\":\"*\",\"update_branch\":false,\"update_branch_stacks\":true}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index fc6e48c563a..c7589758f34 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -722,7 +722,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":2},\"add_labels\":{\"allowed\":[\"smoke-claude\"]},\"add_reviewer\":{\"max\":2,\"target\":\"*\"},\"close_pull_request\":{\"max\":1,\"staged\":true},\"create_check_run\":{\"max\":1,\"name\":\"Smoke Claude: Agent Status\"},\"create_code_scanning_alert\":{\"driver\":\"Smoke Claude\"},\"create_issue\":{\"close_older_issues\":true,\"close_older_key\":\"smoke-claude\",\"expires\":2,\"group\":true,\"labels\":[\"automation\",\"testing\"],\"max\":1},\"create_pull_request_review_comment\":{\"max\":5,\"side\":\"RIGHT\",\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"post_slack_message\":{\"description\":\"Post a message to a fictitious Slack channel (smoke test only — no real Slack integration)\",\"inputs\":{\"channel\":{\"default\":\"#general\",\"description\":\"Slack channel name to post to\",\"required\":false,\"type\":\"string\"},\"message\":{\"description\":\"Message text to post\",\"required\":false,\"type\":\"string\"}}},\"push_to_pull_request_branch\":{\"allowed_files\":[\"smoke-test-files/smoke-claude-push-test.md\"],\"if_no_changes\":\"warn\",\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"CLAUDE.md\",\"AGENTS.md\"],\"required_labels\":[\"smoke-claude\"],\"staged\":true,\"target\":\"*\"},\"report_incomplete\":{},\"resolve_pull_request_review_thread\":{\"max\":5},\"submit_pull_request_review\":{\"footer\":\"always\",\"max\":1},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":true,\"max\":1,\"target\":\"*\",\"update_branch\":false}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":2},\"add_labels\":{\"allowed\":[\"smoke-claude\"]},\"add_reviewer\":{\"max\":2,\"target\":\"*\"},\"close_pull_request\":{\"max\":1,\"staged\":true},\"create_check_run\":{\"max\":1,\"name\":\"Smoke Claude: Agent Status\"},\"create_code_scanning_alert\":{\"driver\":\"Smoke Claude\"},\"create_issue\":{\"close_older_issues\":true,\"close_older_key\":\"smoke-claude\",\"expires\":2,\"group\":true,\"labels\":[\"automation\",\"testing\"],\"max\":1},\"create_pull_request_review_comment\":{\"max\":5,\"side\":\"RIGHT\",\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"post_slack_message\":{\"description\":\"Post a message to a fictitious Slack channel (smoke test only — no real Slack integration)\",\"inputs\":{\"channel\":{\"default\":\"#general\",\"description\":\"Slack channel name to post to\",\"required\":false,\"type\":\"string\"},\"message\":{\"description\":\"Message text to post\",\"required\":false,\"type\":\"string\"}}},\"push_to_pull_request_branch\":{\"allowed_files\":[\"smoke-test-files/smoke-claude-push-test.md\"],\"if_no_changes\":\"warn\",\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"CLAUDE.md\",\"AGENTS.md\"],\"required_labels\":[\"smoke-claude\"],\"staged\":true,\"target\":\"*\"},\"report_incomplete\":{},\"resolve_pull_request_review_thread\":{\"max\":5},\"submit_pull_request_review\":{\"footer\":\"always\",\"max\":1},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":true,\"max\":1,\"target\":\"*\",\"update_branch\":false,\"update_branch_stacks\":true}}" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -2710,7 +2710,7 @@ jobs: GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} GH_AW_SAFE_OUTPUT_SCRIPTS: "{\"post_slack_message\":\"safe_output_script_post_slack_message.cjs\"}" - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":2},\"add_labels\":{\"allowed\":[\"smoke-claude\"]},\"add_reviewer\":{\"max\":2,\"target\":\"*\"},\"close_pull_request\":{\"max\":1,\"staged\":true},\"create_check_run\":{\"max\":1,\"name\":\"Smoke Claude: Agent Status\"},\"create_code_scanning_alert\":{\"driver\":\"Smoke Claude\"},\"create_issue\":{\"close_older_issues\":true,\"close_older_key\":\"smoke-claude\",\"expires\":2,\"group\":true,\"labels\":[\"automation\",\"testing\"],\"max\":1},\"create_pull_request_review_comment\":{\"max\":5,\"side\":\"RIGHT\",\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_to_pull_request_branch\":{\"allowed_files\":[\"smoke-test-files/smoke-claude-push-test.md\"],\"if_no_changes\":\"warn\",\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"CLAUDE.md\",\"AGENTS.md\"],\"required_labels\":[\"smoke-claude\"],\"staged\":true,\"target\":\"*\"},\"report_incomplete\":{},\"resolve_pull_request_review_thread\":{\"max\":5},\"submit_pull_request_review\":{\"footer\":\"always\",\"max\":1},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":true,\"max\":1,\"target\":\"*\",\"update_branch\":false}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":2},\"add_labels\":{\"allowed\":[\"smoke-claude\"]},\"add_reviewer\":{\"max\":2,\"target\":\"*\"},\"close_pull_request\":{\"max\":1,\"staged\":true},\"create_check_run\":{\"max\":1,\"name\":\"Smoke Claude: Agent Status\"},\"create_code_scanning_alert\":{\"driver\":\"Smoke Claude\"},\"create_issue\":{\"close_older_issues\":true,\"close_older_key\":\"smoke-claude\",\"expires\":2,\"group\":true,\"labels\":[\"automation\",\"testing\"],\"max\":1},\"create_pull_request_review_comment\":{\"max\":5,\"side\":\"RIGHT\",\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_to_pull_request_branch\":{\"allowed_files\":[\"smoke-test-files/smoke-claude-push-test.md\"],\"if_no_changes\":\"warn\",\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"CLAUDE.md\",\"AGENTS.md\"],\"required_labels\":[\"smoke-claude\"],\"staged\":true,\"target\":\"*\"},\"report_incomplete\":{},\"resolve_pull_request_review_thread\":{\"max\":5},\"submit_pull_request_review\":{\"footer\":\"always\",\"max\":1},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":true,\"max\":1,\"target\":\"*\",\"update_branch\":false,\"update_branch_stacks\":true}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/actions/setup/js/update_pull_request.cjs b/actions/setup/js/update_pull_request.cjs index 2b401b68578..e5a96210ca1 100644 --- a/actions/setup/js/update_pull_request.cjs +++ b/actions/setup/js/update_pull_request.cjs @@ -174,10 +174,12 @@ async function executePRUpdate(github, context, prNumber, updateData) { const includeFooter = updateData._includeFooter !== false; // Default to true // Remove internal fields (including update_branch which is handled separately below) - const { _operation, _rawBody, _includeFooter, _workflowRepo, update_branch, ...apiData } = updateData; + const { _operation, _rawBody, _includeFooter, _workflowRepo, _update_branch_stacks, update_branch, ...apiData } = updateData; const updateBranch = update_branch === true; + const updateBranchStacksEnabled = _update_branch_stacks !== false; if (updateBranch) { + core.info(`update_branch stacked PR sync fallback is ${updateBranchStacksEnabled ? "enabled" : "disabled"} for pull request #${prNumber}`); core.info(`Updating pull request #${prNumber} branch with base branch changes`); try { await withRetry( @@ -198,10 +200,16 @@ async function executePRUpdate(github, context, prNumber, updateData) { } catch (error) { const errorMessage = getErrorMessage(error); if (isStackedPRUnsupportedUpdateBranchError(error)) { - const didSyncStack = await tryStackedPRUpdateBranch(github, context, prNumber); - if (didSyncStack) { - core.info(`Updated stacked PR #${prNumber} branch via stack sync`); + if (updateBranchStacksEnabled) { + core.info(`Attempting stacked PR stack-sync fallback for pull request #${prNumber} after update_branch 422 unsupported response`); + const didSyncStack = await tryStackedPRUpdateBranch(github, context, prNumber); + if (didSyncStack) { + core.info(`Updated stacked PR #${prNumber} branch via stack sync`); + } else { + core.warning(`Failed to update pull request #${prNumber} branch from base (non-fatal): ${errorMessage}`); + } } else { + core.info(`Skipping stacked PR stack-sync fallback for pull request #${prNumber}: update_branch_stacks=false`); core.warning(`Failed to update pull request #${prNumber} branch from base (non-fatal): ${errorMessage}`); } } else if (isNonFatalUpdateBranchError(error)) { @@ -300,6 +308,8 @@ function buildPRUpdateData(item, config) { } const updateBranch = item.update_branch !== undefined ? item.update_branch === true : config.update_branch === true; + const updateBranchStacksEnabled = config.update_branch_stacks !== false; + updateData._update_branch_stacks = updateBranchStacksEnabled; if (updateBranch) { updateData.update_branch = true; hasUpdates = true; @@ -347,6 +357,7 @@ const main = createUpdateHandlerFactory({ allow_title: true, allow_body: true, update_branch: false, + update_branch_stacks: true, }, itemFilter: async (githubClient, repoParts, prNumber, config) => { const requiredLabels = Array.isArray(config.required_labels) ? config.required_labels : []; diff --git a/actions/setup/js/update_pull_request.test.cjs b/actions/setup/js/update_pull_request.test.cjs index 5f6d21fe1cb..94d4eebaa0b 100644 --- a/actions/setup/js/update_pull_request.test.cjs +++ b/actions/setup/js/update_pull_request.test.cjs @@ -812,6 +812,20 @@ describe("update_pull_request.cjs - update_branch behavior", () => { expect(result.data.update_branch).toBe(true); }); + it("should default stacked PR stack-sync fallback to enabled when update_branch is set", () => { + const result = updatePRModule.buildPRUpdateData({ update_branch: true }, {}); + + expect(result.success).toBe(true); + expect(result.data._update_branch_stacks).toBe(true); + }); + + it("should disable stacked PR stack-sync fallback when update_branch_stacks is false", () => { + const result = updatePRModule.buildPRUpdateData({ update_branch: true }, { update_branch_stacks: false }); + + expect(result.success).toBe(true); + expect(result.data._update_branch_stacks).toBe(false); + }); + it("should call updateBranch when update_branch is enabled and no other fields are updated", async () => { const handler = await updatePRModule.main({ update_branch: true }); @@ -1109,6 +1123,30 @@ describe("update_pull_request.cjs - update_branch behavior", () => { expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("branch from base (non-fatal)")); }); + it("should keep stacked-PR unsupported non-fatal without stack sync when update_branch_stacks is false", async () => { + const stackedPRError = new Error("Updating a stacked PR's branch via this endpoint is not supported."); + stackedPRError.status = 422; + mockGithub.rest.pulls.updateBranch.mockRejectedValueOnce(stackedPRError); + + const handler = await updatePRModule.main({ update_branch: true, update_branch_stacks: false }); + 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.request).not.toHaveBeenCalled(); + expect(mockGithub.rest.pulls.update).toHaveBeenCalledWith({ + owner: "testowner", + repo: "testrepo", + pull_number: 100, + title: "Updated PR", + }); + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Skipping stacked PR stack-sync fallback")); + expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("branch from base (non-fatal)")); + }); + it("should keep stacked-PR unsupported fatal when updateBranch status is not 422", async () => { const stackedPRError = new Error("Updating a stacked PR's branch via this endpoint is not supported."); stackedPRError.status = 403; diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 96a33fd14a9..66c7cba6721 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -9297,6 +9297,10 @@ "type": "boolean", "description": "When true, update the pull request branch with the latest base branch changes before applying other updates. Defaults to false." }, + "update-branch.stacks": { + "type": "boolean", + "description": "When true, allow stacked-PR stack-sync fallback when update-branch is unsupported. Defaults to true. Set to false to disable stacked-PR sync fallback." + }, "operation": { "type": "string", "description": "Default operation for body updates: 'append' (add to end), 'prepend' (add to start), or 'replace' (overwrite completely). Defaults to 'replace' if not specified.", diff --git a/pkg/workflow/compiler_safe_outputs_config_test.go b/pkg/workflow/compiler_safe_outputs_config_test.go index bece1a35bd4..77d5201f76c 100644 --- a/pkg/workflow/compiler_safe_outputs_config_test.go +++ b/pkg/workflow/compiler_safe_outputs_config_test.go @@ -1388,19 +1388,25 @@ func TestHandlerConfigUpdateFields(t *testing.T) { func TestUpdatePullRequestUpdateBranchHandlerConfig(t *testing.T) { tests := []struct { - name string - updateBranch *bool - expected bool + name string + updateBranch *bool + updateBranchStacks *bool + expectedBranch bool + expectedStacks bool }{ { - name: "defaults update_branch to false", - updateBranch: nil, - expected: false, + name: "defaults update_branch to false and update_branch_stacks to true", + updateBranch: nil, + updateBranchStacks: nil, + expectedBranch: false, + expectedStacks: true, }, { - name: "sets update_branch true when configured", - updateBranch: testBoolPtr(true), - expected: true, + name: "sets update_branch true and update_branch_stacks false when configured", + updateBranch: testBoolPtr(true), + updateBranchStacks: testBoolPtr(false), + expectedBranch: true, + expectedStacks: false, }, } @@ -1412,7 +1418,8 @@ func TestUpdatePullRequestUpdateBranchHandlerConfig(t *testing.T) { Name: "Test Workflow", SafeOutputs: &SafeOutputsConfig{ UpdatePullRequests: &UpdatePullRequestsConfig{ - UpdateBranch: tt.updateBranch, + UpdateBranch: tt.updateBranch, + UpdateBranchStacks: tt.updateBranchStacks, }, }, } @@ -1439,7 +1446,11 @@ func TestUpdatePullRequestUpdateBranchHandlerConfig(t *testing.T) { updateBranchValue, ok := updatePRConfig["update_branch"] require.True(t, ok, "Expected update_branch key in update_pull_request config") - assert.Equal(t, tt.expected, updateBranchValue) + assert.Equal(t, tt.expectedBranch, updateBranchValue) + + updateBranchStacksValue, ok := updatePRConfig["update_branch_stacks"] + require.True(t, ok, "Expected update_branch_stacks key in update_pull_request config") + assert.Equal(t, tt.expectedStacks, updateBranchStacksValue) } } } diff --git a/pkg/workflow/safe_outputs_handler_registry.go b/pkg/workflow/safe_outputs_handler_registry.go index ea70f755340..03dd04a0902 100644 --- a/pkg/workflow/safe_outputs_handler_registry.go +++ b/pkg/workflow/safe_outputs_handler_registry.go @@ -678,6 +678,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddBoolPtrOrDefault("allow_title", c.Title, true). AddBoolPtrOrDefault("allow_body", c.Body, true). AddBoolPtrOrDefault("update_branch", c.UpdateBranch, false). + AddBoolPtrOrDefault("update_branch_stacks", c.UpdateBranchStacks, true). AddStringPtr("default_operation", c.Operation). AddTemplatableBool("footer", getEffectiveFooterForTemplatable(c.Footer, cfg.Footer)).AddStringSlice("required_labels", c.RequiredLabels). AddIfNotEmpty("required_title_prefix", c.RequiredTitlePrefix).AddIfNotEmpty("target-repo", c.TargetRepoSlug). diff --git a/pkg/workflow/update_pull_request.go b/pkg/workflow/update_pull_request.go index 1337f422b29..3ee212825a4 100644 --- a/pkg/workflow/update_pull_request.go +++ b/pkg/workflow/update_pull_request.go @@ -13,11 +13,12 @@ var updatePullRequestLog = logger.New("workflow:update_pull_request") type UpdatePullRequestsConfig struct { UpdateEntityConfig `yaml:",inline"` SafeOutputFilterConfig `yaml:",inline"` - Title *bool `yaml:"title,omitempty"` // Allow updating PR title - defaults to true, set to false to disable - Body *bool `yaml:"body,omitempty"` // Allow updating PR body - defaults to true, set to false to disable - UpdateBranch *bool `yaml:"update-branch,omitempty"` // When true, update PR branch with latest base branch changes before applying other updates. Defaults to false. - Operation *string `yaml:"operation,omitempty"` // Default operation for body updates: "append", "prepend", or "replace" (defaults to "replace") - Footer *string `yaml:"footer,omitempty"` // Controls whether AI-generated footer is added. When false, visible footer is omitted. + Title *bool `yaml:"title,omitempty"` // Allow updating PR title - defaults to true, set to false to disable + Body *bool `yaml:"body,omitempty"` // Allow updating PR body - defaults to true, set to false to disable + UpdateBranch *bool `yaml:"update-branch,omitempty"` // When true, update PR branch with latest base branch changes before applying other updates. Defaults to false. + UpdateBranchStacks *bool `yaml:"update-branch.stacks,omitempty"` // When true, allow stacked-PR stack-sync fallback if update-branch endpoint is unsupported. Defaults to true. + Operation *string `yaml:"operation,omitempty"` // Default operation for body updates: "append", "prepend", or "replace" (defaults to "replace") + Footer *string `yaml:"footer,omitempty"` // Controls whether AI-generated footer is added. When false, visible footer is omitted. } // parseUpdatePullRequestsConfig handles update-pull-request configuration @@ -31,6 +32,7 @@ func (c *Compiler) parseUpdatePullRequestsConfig(outputMap map[string]any) *Upda {Name: "title", Mode: FieldParsingBoolValue, Dest: &cfg.Title}, {Name: "body", Mode: FieldParsingBoolValue, Dest: &cfg.Body}, {Name: "update-branch", Mode: FieldParsingBoolValue, Dest: &cfg.UpdateBranch}, + {Name: "update-branch.stacks", Mode: FieldParsingBoolValue, Dest: &cfg.UpdateBranchStacks}, updateEntityFooterField(&cfg.Footer), } }, func(configMap map[string]any, cfg *UpdatePullRequestsConfig) { diff --git a/pkg/workflow/update_pull_request_test.go b/pkg/workflow/update_pull_request_test.go index 4ea89e40720..ea32beb7377 100644 --- a/pkg/workflow/update_pull_request_test.go +++ b/pkg/workflow/update_pull_request_test.go @@ -29,6 +29,7 @@ safe-outputs: required-title-prefix: "[ci] " required-labels: [automation, bot] body: true + update-branch.stacks: false --- # Test Update Pull Request Required Filters @@ -70,4 +71,12 @@ This workflow tests the update-pull-request required-labels and required-title-p if workflowData.SafeOutputs.UpdatePullRequests.RequiredLabels[1] != "bot" { t.Fatalf("Expected second required label to be 'bot', got '%s'", workflowData.SafeOutputs.UpdatePullRequests.RequiredLabels[1]) } + + if workflowData.SafeOutputs.UpdatePullRequests.UpdateBranchStacks == nil { + t.Fatal("Expected update-branch.stacks to be parsed") + } + + if *workflowData.SafeOutputs.UpdatePullRequests.UpdateBranchStacks { + t.Fatal("Expected update-branch.stacks to be false") + } } From 0384389b9a29e7cc53c788cfc5ce145f5b9e094f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Aug 2026 13:04:16 +0000 Subject: [PATCH 7/8] Address PR-finisher review feedback for stacked PR fallback Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/update_pull_request.cjs | 33 ++++--------------- actions/setup/js/update_pull_request.test.cjs | 6 ++++ 2 files changed, 13 insertions(+), 26 deletions(-) diff --git a/actions/setup/js/update_pull_request.cjs b/actions/setup/js/update_pull_request.cjs index e5a96210ca1..fe64167dcff 100644 --- a/actions/setup/js/update_pull_request.cjs +++ b/actions/setup/js/update_pull_request.cjs @@ -64,10 +64,6 @@ function isNonFatalUpdateBranchError(error) { // GitHub update-branch API also returns 403 with this message when a PR contains workflow // file changes and the check times out, rather than the usual "refusing to allow" phrase. const hasWorkflowsScopeRequired = message.includes("`workflows` scope may be required") || message.includes("unable to determine if workflow can be created or updated"); - if (isStackedPRUnsupportedUpdateBranchError(error)) { - core.info(`Treating update-branch error as non-fatal: stacked PR branch update endpoint unsupported (status=${status ?? "unknown"})`); - return true; - } if (status !== undefined) { if (status === 403 && (hasWorkflowsPermissionError || hasWorkflowsScopeRequired)) { @@ -111,33 +107,18 @@ async function tryStackedPRUpdateBranch(github, context, prNumber) { let stackNumber; try { - const { data: pullRequest } = await github.rest.pulls.get({ + const { data: stacks } = await github.request("GET /repos/{owner}/{repo}/stacks", { owner: context.repo.owner, repo: context.repo.repo, - pull_number: prNumber, + pull_request: prNumber, + per_page: 1, }); - if (typeof pullRequest?.stack?.number === "number") { - stackNumber = pullRequest.stack.number; + if (Array.isArray(stacks) && stacks.length > 0 && typeof stacks[0]?.number === "number") { + stackNumber = stacks[0].number; } } catch (error) { - core.info(`Unable to resolve stack number from pull metadata for #${prNumber}: ${getErrorMessage(error)}; trying stack list lookup`); - } - - if (stackNumber === undefined) { - try { - const { data: stacks } = await github.request("GET /repos/{owner}/{repo}/stacks", { - owner: context.repo.owner, - repo: context.repo.repo, - pull_request: prNumber, - per_page: 1, - }); - if (Array.isArray(stacks) && stacks.length > 0 && typeof stacks[0]?.number === "number") { - stackNumber = stacks[0].number; - } - } catch (error) { - core.info(`Unable to resolve stack number from stack list for #${prNumber}: ${getErrorMessage(error)}; skipping stack sync`); - return false; - } + core.info(`Unable to resolve stack number from stack list for #${prNumber}: ${getErrorMessage(error)}; skipping stack sync`); + return false; } if (stackNumber === undefined) { diff --git a/actions/setup/js/update_pull_request.test.cjs b/actions/setup/js/update_pull_request.test.cjs index 94d4eebaa0b..01e27db569a 100644 --- a/actions/setup/js/update_pull_request.test.cjs +++ b/actions/setup/js/update_pull_request.test.cjs @@ -1043,6 +1043,12 @@ describe("update_pull_request.cjs - update_branch behavior", () => { expect(result.success).toBe(true); expect(mockGithub.rest.pulls.updateBranch).toHaveBeenCalledTimes(1); + expect(mockGithub.request).toHaveBeenCalledWith("GET /repos/{owner}/{repo}/stacks", { + owner: "testowner", + repo: "testrepo", + pull_request: 100, + per_page: 1, + }); expect(mockGithub.rest.pulls.update).toHaveBeenCalledWith({ owner: "testowner", repo: "testrepo", From 88618003d0cd2adfbcf1e9cc37f155de7ed4ee82 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Aug 2026 13:35:22 +0000 Subject: [PATCH 8/8] Fix brittle daily regression audit workflow contract test Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- ...regression_audit_workflow_contract_test.go | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/pkg/cli/daily_regression_audit_workflow_contract_test.go b/pkg/cli/daily_regression_audit_workflow_contract_test.go index ffde98d68b3..563ea557dcb 100644 --- a/pkg/cli/daily_regression_audit_workflow_contract_test.go +++ b/pkg/cli/daily_regression_audit_workflow_contract_test.go @@ -5,10 +5,10 @@ package cli import ( "os" "path/filepath" + "strings" "testing" "github.com/github/gh-aw/pkg/gitutil" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -19,10 +19,23 @@ func TestDailyRegressionAuditAllowsPythonJSONParsing(t *testing.T) { workflowPath := filepath.Join(repoRoot, ".github", "workflows", "daily-regression-audit-kiro.md") content, err := os.ReadFile(workflowPath) require.NoError(t, err) - assert.Contains(t, string(content), "- python3") + workflowContent := string(content) + hasPython3 := strings.Contains(workflowContent, "- python3") + hasJQ := strings.Contains(workflowContent, "- jq") + require.True(t, hasPython3 || hasJQ, "workflow must allow either python3 or jq for JSON parsing") lockPath := filepath.Join(repoRoot, ".github", "workflows", "daily-regression-audit-kiro.lock.yml") lockContent, err := os.ReadFile(lockPath) require.NoError(t, err) - assert.Contains(t, string(lockContent), "--allow-tool shell(python3)") + compiled := string(lockContent) + if hasPython3 { + require.Contains(t, compiled, "--allow-tool shell(python3)") + } else { + require.NotContains(t, compiled, "--allow-tool shell(python3)") + } + if hasJQ { + require.Contains(t, compiled, "--allow-tool shell(jq)") + } else { + require.NotContains(t, compiled, "--allow-tool shell(jq)") + } }