From 9169dff5e425e037e29141f4841bcb5556f47267 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Jul 2026 13:40:27 +0000 Subject: [PATCH 1/3] Initial plan From 95bf327cd8417b476d6b599227bc44ae688cfce7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Jul 2026 13:59:09 +0000 Subject: [PATCH 2/3] fix: handle workflows-scope-required 403 as non-fatal and add token fallback - Extend isNonFatalUpdateBranchError in update_pull_request.cjs to treat 403 errors with "workflows scope may be required" or "unable to determine if workflow can be created or updated" as non-fatal. This matches the GitHub API error returned when a PR contains workflow file changes and the token lacks workflows scope, preventing a spurious update_pull_request failure. - Add test coverage for the new non-fatal 403 pattern. - In pr-sous-chef.md (and recompiled lock.yml), update add-comment github-token to use AWI_MAINTENANCE_TOKEN || GH_AW_GITHUB_TOKEN || GITHUB_TOKEN, providing a fallback when AWI_MAINTENANCE_TOKEN is not configured. Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../daily-graft-intelligence.lock.yml | 6 ++--- .github/workflows/pr-sous-chef.lock.yml | 10 +++++--- .github/workflows/pr-sous-chef.md | 2 +- actions/setup/js/update_pull_request.cjs | 7 ++++-- actions/setup/js/update_pull_request.test.cjs | 25 +++++++++++++++++++ 5 files changed, 40 insertions(+), 10 deletions(-) diff --git a/.github/workflows/daily-graft-intelligence.lock.yml b/.github/workflows/daily-graft-intelligence.lock.yml index 6c73e6c8fc4..d534ea10727 100644 --- a/.github/workflows/daily-graft-intelligence.lock.yml +++ b/.github/workflows/daily-graft-intelligence.lock.yml @@ -129,7 +129,7 @@ jobs: env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" - GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-5' }} + GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }} GH_AW_INFO_VERSION: "1.0.75" GH_AW_INFO_AGENT_VERSION: "1.0.75" GH_AW_INFO_WORKFLOW_NAME: "Daily Graft Intelligence" @@ -901,7 +901,7 @@ jobs: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ github.token }} - COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-5' }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }} COPILOT_SDK_URI: http://127.0.0.1:3002 GH_AW_COPILOT_SDK_DRIVER: 1 GH_AW_COPILOT_SDK_SERVER_ARGS: '["--headless","--no-auto-update","--port","3002","--add-dir","/tmp/gh-aw/","--log-level","all","--log-dir","/tmp/gh-aw/sandbox/agent/logs/","--disable-builtin-mcps","--no-ask-user","--allow-tool","github","--allow-tool","graft","--allow-tool","graft(graft_ask)","--allow-tool","graft(graft_callers)","--allow-tool","graft(graft_check)","--allow-tool","graft(graft_grep)","--allow-tool","graft(graft_map)","--allow-tool","graft(graft_skeleton)","--allow-tool","safeoutputs","--allow-tool","shell(cat /tmp/gh-aw/agent/graft-changed-files.txt)","--allow-tool","shell(cat /tmp/gh-aw/agent/graft-recent-activity.txt)","--allow-tool","shell(cat)","--allow-tool","shell(date)","--allow-tool","shell(echo)","--allow-tool","shell(gh:*)","--allow-tool","shell(graft:*)","--allow-tool","shell(grep)","--allow-tool","shell(head)","--allow-tool","shell(ls)","--allow-tool","shell(printf)","--allow-tool","shell(pwd)","--allow-tool","shell(safeoutputs:*)","--allow-tool","shell(sort)","--allow-tool","shell(tail)","--allow-tool","shell(uniq)","--allow-tool","shell(wc)","--allow-tool","shell(yq)","--allow-tool","write","--allow-all-paths"]' @@ -1533,7 +1533,7 @@ jobs: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ github.token }} - COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-5' }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }} GH_AW_LLM_PROVIDER: github GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }} GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} diff --git a/.github/workflows/pr-sous-chef.lock.yml b/.github/workflows/pr-sous-chef.lock.yml index c369e03b9db..82e9197bb25 100644 --- a/.github/workflows/pr-sous-chef.lock.yml +++ b/.github/workflows/pr-sous-chef.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"aa09c4ac6bec721c6a54a653fb9cfba588ad81cbe2dddeb8402f6f91f137e4a2","body_hash":"42311cdc89e4da6ce38632900fcd3642871f817acd502088a4bf43e0b2433cd3","strict":true,"agent_id":"pi","agent_model":"copilot/gpt-5.4","engine_versions":{"pi":"0.82.1"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"56ba9f0125ea80787b04acd33469fe743ced157b55dca6648362ad522a2af1de","body_hash":"42311cdc89e4da6ce38632900fcd3642871f817acd502088a4bf43e0b2433cd3","strict":true,"agent_id":"pi","agent_model":"copilot/gpt-5.4","engine_versions":{"pi":"0.82.1"}} # gh-aw-manifest: {"version":1,"secrets":["AWI_MAINTENANCE_TOKEN","COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.42","digest":"sha256:26a8af4e5566485b02f52af59ee03803ae798271a9619d4767e94d07806deb9b","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.42@sha256:26a8af4e5566485b02f52af59ee03803ae798271a9619d4767e94d07806deb9b"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.42","digest":"sha256:944f2686c9ab9bec338fd14b662461662f77cd12cd0ea8a3e7cb8c0987cd1607","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.42@sha256:944f2686c9ab9bec338fd14b662461662f77cd12cd0ea8a3e7cb8c0987cd1607"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.42","digest":"sha256:da006bf96d2d246dd269d57b233c1798d2ad63d6cd64ca02f7bf71045028781f","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.42@sha256:da006bf96d2d246dd269d57b233c1798d2ad63d6cd64ca02f7bf71045028781f"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.42","digest":"sha256:42dfeb649c680a8558cd5423dbc530b653a69413e35ffbe5e71da5d48c94bdf0","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.42@sha256:42dfeb649c680a8558cd5423dbc530b653a69413e35ffbe5e71da5d48c94bdf0"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.6","digest":"sha256:fecabec51bbc41f2ad61076d6bcd9a36ef23b142e672a444e054d37fc29de93c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.6@sha256:fecabec51bbc41f2ad61076d6bcd9a36ef23b142e672a444e054d37fc29de93c"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:a8082161d7dceda14b68f32eb39d0eaa96b825d07f5895b096afab9d9e0c7748","pinned_image":"ghcr.io/github/gh-aw-node@sha256:a8082161d7dceda14b68f32eb39d0eaa96b825d07f5895b096afab9d9e0c7748"},{"image":"ghcr.io/github/github-mcp-server:v1.7.0","digest":"sha256:c491ffdf6f4c85cb5397021bc655edb8ab825c6f5f568e7597d77a1bd7c4d308","pinned_image":"ghcr.io/github/github-mcp-server:v1.7.0@sha256:c491ffdf6f4c85cb5397021bc655edb8ab825c6f5f568e7597d77a1bd7c4d308"}]} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -873,7 +873,9 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.42@sha256:26a8af4e5566485b02f52af59ee03803ae798271a9619d4767e94d07806deb9b ghcr.io/github/gh-aw-firewall/api-proxy:0.27.42@sha256:944f2686c9ab9bec338fd14b662461662f77cd12cd0ea8a3e7cb8c0987cd1607 ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.42@sha256:da006bf96d2d246dd269d57b233c1798d2ad63d6cd64ca02f7bf71045028781f ghcr.io/github/gh-aw-firewall/squid:0.27.42@sha256:42dfeb649c680a8558cd5423dbc530b653a69413e35ffbe5e71da5d48c94bdf0 ghcr.io/github/gh-aw-mcpg:v0.4.6@sha256:fecabec51bbc41f2ad61076d6bcd9a36ef23b142e672a444e054d37fc29de93c ghcr.io/github/gh-aw-node@sha256:a8082161d7dceda14b68f32eb39d0eaa96b825d07f5895b096afab9d9e0c7748 ghcr.io/github/github-mcp-server:v1.7.0@sha256:c491ffdf6f4c85cb5397021bc655edb8ab825c6f5f568e7597d77a1bd7c4d308 - name: Generate Safe Outputs Config env: - GH_AW_SECRET_AWI_MAINTENANCE_TOKEN: ${{ secrets.AWI_MAINTENANCE_TOKEN }} + 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 }} run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs @@ -1751,7 +1753,7 @@ jobs: GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} GH_AW_SAFE_OUTPUT_MESSAGES: "{\"runStarted\":\"🍳 [{workflow_name}]({run_url}) is preparing PRs for maintainer investigation.\",\"runSuccess\":\"✅ [{workflow_name}]({run_url}) finished PR sous-chef nudges.\",\"runFailure\":\"⚠️ [{workflow_name}]({run_url}) {status} while preparing PRs.\"}" with: - github-token: ${{ secrets.AWI_MAINTENANCE_TOKEN }} + github-token: ${{ secrets.AWI_MAINTENANCE_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); @@ -2483,7 +2485,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 }}\",\"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}}" 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-sous-chef.md b/.github/workflows/pr-sous-chef.md index 8350cc97f0f..74099d8ee69 100644 --- a/.github/workflows/pr-sous-chef.md +++ b/.github/workflows/pr-sous-chef.md @@ -259,7 +259,7 @@ safe-outputs: add-comment: max: 4 target: "*" - github-token: ${{ secrets.AWI_MAINTENANCE_TOKEN }} + github-token: ${{ secrets.AWI_MAINTENANCE_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} resolve-pull-request-review-thread: max: 40 dismiss-pull-request-review: diff --git a/actions/setup/js/update_pull_request.cjs b/actions/setup/js/update_pull_request.cjs index a80344b526d..c552e9d0327 100644 --- a/actions/setup/js/update_pull_request.cjs +++ b/actions/setup/js/update_pull_request.cjs @@ -38,9 +38,12 @@ 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")); + // 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 (status !== undefined) { - if (status === 403 && hasWorkflowsPermissionError) { + if (status === 403 && (hasWorkflowsPermissionError || hasWorkflowsScopeRequired)) { return true; } if (status !== 422) { @@ -52,7 +55,7 @@ function isNonFatalUpdateBranchError(error) { // - 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. - 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 || hasWorkflowsScopeRequired; } /** diff --git a/actions/setup/js/update_pull_request.test.cjs b/actions/setup/js/update_pull_request.test.cjs index 6fb5e16ae11..cebb17b3782 100644 --- a/actions/setup/js/update_pull_request.test.cjs +++ b/actions/setup/js/update_pull_request.test.cjs @@ -941,4 +941,29 @@ 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 gets workflows-scope-required 403", async () => { + const scopeError = new Error("Unable to determine if workflow can be created or updated due to timeout; `workflows` scope may be required. - https://docs.github.com/rest/pulls/pulls#update-a-pull-request-branch"); + scopeError.status = 403; + // The message contains "timeout" which makes isTransientError return true, so withRetry + // retries once (maxRetries: 1). Both attempts must fail to reach the non-fatal catch path. + mockGithub.rest.pulls.updateBranch.mockRejectedValue(scopeError); + + const handler = await updatePRModule.main({ update_branch: true }); + const result = await handler({ + pull_request_number: 100, + title: "Updated PR", + }); + + expect(result.success).toBe(true); + // Called twice: initial attempt + 1 retry (maxRetries: 1 in executePRUpdate) + expect(mockGithub.rest.pulls.updateBranch).toHaveBeenCalledTimes(2); + 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)")); + }); }); From acb95f312b0e3ec0027ac7cb13dd232b803e1c67 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Jul 2026 14:30:33 +0000 Subject: [PATCH 3/3] Apply remaining changes Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../daily-graft-intelligence.lock.yml | 6 ++-- actions/setup/js/update_pull_request.cjs | 6 +++- actions/setup/js/update_pull_request.test.cjs | 33 +++++++++++++++++-- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/.github/workflows/daily-graft-intelligence.lock.yml b/.github/workflows/daily-graft-intelligence.lock.yml index d534ea10727..6c73e6c8fc4 100644 --- a/.github/workflows/daily-graft-intelligence.lock.yml +++ b/.github/workflows/daily-graft-intelligence.lock.yml @@ -129,7 +129,7 @@ jobs: env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" - GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }} + GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-5' }} GH_AW_INFO_VERSION: "1.0.75" GH_AW_INFO_AGENT_VERSION: "1.0.75" GH_AW_INFO_WORKFLOW_NAME: "Daily Graft Intelligence" @@ -901,7 +901,7 @@ jobs: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ github.token }} - COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-5' }} COPILOT_SDK_URI: http://127.0.0.1:3002 GH_AW_COPILOT_SDK_DRIVER: 1 GH_AW_COPILOT_SDK_SERVER_ARGS: '["--headless","--no-auto-update","--port","3002","--add-dir","/tmp/gh-aw/","--log-level","all","--log-dir","/tmp/gh-aw/sandbox/agent/logs/","--disable-builtin-mcps","--no-ask-user","--allow-tool","github","--allow-tool","graft","--allow-tool","graft(graft_ask)","--allow-tool","graft(graft_callers)","--allow-tool","graft(graft_check)","--allow-tool","graft(graft_grep)","--allow-tool","graft(graft_map)","--allow-tool","graft(graft_skeleton)","--allow-tool","safeoutputs","--allow-tool","shell(cat /tmp/gh-aw/agent/graft-changed-files.txt)","--allow-tool","shell(cat /tmp/gh-aw/agent/graft-recent-activity.txt)","--allow-tool","shell(cat)","--allow-tool","shell(date)","--allow-tool","shell(echo)","--allow-tool","shell(gh:*)","--allow-tool","shell(graft:*)","--allow-tool","shell(grep)","--allow-tool","shell(head)","--allow-tool","shell(ls)","--allow-tool","shell(printf)","--allow-tool","shell(pwd)","--allow-tool","shell(safeoutputs:*)","--allow-tool","shell(sort)","--allow-tool","shell(tail)","--allow-tool","shell(uniq)","--allow-tool","shell(wc)","--allow-tool","shell(yq)","--allow-tool","write","--allow-all-paths"]' @@ -1533,7 +1533,7 @@ jobs: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ github.token }} - COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-5' }} GH_AW_LLM_PROVIDER: github GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }} GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} diff --git a/actions/setup/js/update_pull_request.cjs b/actions/setup/js/update_pull_request.cjs index c552e9d0327..bc515403ff1 100644 --- a/actions/setup/js/update_pull_request.cjs +++ b/actions/setup/js/update_pull_request.cjs @@ -55,7 +55,11 @@ function isNonFatalUpdateBranchError(error) { // - 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. - return message.includes("there are no new commits on the base branch") || message.includes("merge conflict between base and head") || hasWorkflowsPermissionError || hasWorkflowsScopeRequired; + // hasWorkflowsPermissionError / hasWorkflowsScopeRequired are only checked here for errors + // with no numeric status (status === undefined). The explicit 403 case is already handled + // by the if-block above, and other numeric statuses (e.g. 422 with these phrases) should + // not be silently swallowed. + return message.includes("there are no new commits on the base branch") || message.includes("merge conflict between base and head") || ((hasWorkflowsPermissionError || hasWorkflowsScopeRequired) && status === undefined); } /** diff --git a/actions/setup/js/update_pull_request.test.cjs b/actions/setup/js/update_pull_request.test.cjs index cebb17b3782..9dd70eecd49 100644 --- a/actions/setup/js/update_pull_request.test.cjs +++ b/actions/setup/js/update_pull_request.test.cjs @@ -942,11 +942,13 @@ 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 gets workflows-scope-required 403", async () => { - const scopeError = new Error("Unable to determine if workflow can be created or updated due to timeout; `workflows` scope may be required. - https://docs.github.com/rest/pulls/pulls#update-a-pull-request-branch"); + 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."); scopeError.status = 403; // The message contains "timeout" which makes isTransientError return true, so withRetry - // retries once (maxRetries: 1). Both attempts must fail to reach the non-fatal catch path. + // retries once (maxRetries: 1, see executePRUpdate). Both attempts must fail to reach the + // non-fatal catch path. Update this assertion if maxRetries changes. mockGithub.rest.pulls.updateBranch.mockRejectedValue(scopeError); const handler = await updatePRModule.main({ update_branch: true }); @@ -966,4 +968,29 @@ 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 gets unable-to-determine-workflow 403 (unable-to-determine variant)", async () => { + // Message matches only the "unable to determine if workflow can be created or updated" branch + // of hasWorkflowsScopeRequired, independently of the scope-phrase variant above. + const unableToDetermineError = new Error("Unable to determine if workflow can be created or updated; contact support."); + unableToDetermineError.status = 403; + // No "timeout" in the message, so isTransientError returns false — no retry, called once. + mockGithub.rest.pulls.updateBranch.mockRejectedValueOnce(unableToDetermineError); + + 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)")); + }); });