From 192cf86a1b1d7e148db6b4889bc24a45ab9ec1ab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Jun 2026 22:49:53 +0000 Subject: [PATCH 1/8] Start: add pre-flight and full-branch allowed_files validation for safe output server Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/daily-team-status.lock.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/daily-team-status.lock.yml b/.github/workflows/daily-team-status.lock.yml index 12b2c936b1f..e48da18cca8 100644 --- a/.github/workflows/daily-team-status.lock.yml +++ b/.github/workflows/daily-team-status.lock.yml @@ -1,5 +1,5 @@ # gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"9f61ffba0d4e9663a43f41030c13a4c7c71ebac8122f586cbd706756a605df94","body_hash":"33c10cc22b8836b79387efda582e48c5a463e9849880a01d58704a0fa291e986","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","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":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.16","digest":"sha256:94bbf52b74d38e8117387e93e698f79d678dd3879faa0e57f2ea128eda8fb507","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.16@sha256:94bbf52b74d38e8117387e93e698f79d678dd3879faa0e57f2ea128eda8fb507"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16","digest":"sha256:bbad2f109b97a4b3375ad371a5300d42bc9251dad61cd7bc66380cad8501cf94","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16@sha256:bbad2f109b97a4b3375ad371a5300d42bc9251dad61cd7bc66380cad8501cf94"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.16","digest":"sha256:483d6a8086752a02d581d7a42629b741e3f2fa9f3a6a10320590cf881638dad3","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.16@sha256:483d6a8086752a02d581d7a42629b741e3f2fa9f3a6a10320590cf881638dad3"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.32","digest":"sha256:63e46b56dfd70895a701b6fc6dd0189e11e2d875f327f1781e81b31848735477","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.32@sha256:63e46b56dfd70895a701b6fc6dd0189e11e2d875f327f1781e81b31848735477"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.5.0","digest":"sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4","pinned_image":"ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4"}]} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","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":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.16"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.16"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.32","digest":"sha256:63e46b56dfd70895a701b6fc6dd0189e11e2d875f327f1781e81b31848735477","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.32@sha256:63e46b56dfd70895a701b6fc6dd0189e11e2d875f327f1781e81b31848735477"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.5.0","digest":"sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4","pinned_image":"ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4"}]} # 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 # # ___ _ _ @@ -47,9 +47,9 @@ # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.27.16@sha256:94bbf52b74d38e8117387e93e698f79d678dd3879faa0e57f2ea128eda8fb507 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16@sha256:bbad2f109b97a4b3375ad371a5300d42bc9251dad61cd7bc66380cad8501cf94 -# - ghcr.io/github/gh-aw-firewall/squid:0.27.16@sha256:483d6a8086752a02d581d7a42629b741e3f2fa9f3a6a10320590cf881638dad3 +# - ghcr.io/github/gh-aw-firewall/agent:0.27.16 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16 +# - ghcr.io/github/gh-aw-firewall/squid:0.27.16 # - ghcr.io/github/gh-aw-mcpg:v0.3.32@sha256:63e46b56dfd70895a701b6fc6dd0189e11e2d875f327f1781e81b31848735477 # - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b # - ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 @@ -507,7 +507,7 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.16@sha256:94bbf52b74d38e8117387e93e698f79d678dd3879faa0e57f2ea128eda8fb507 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16@sha256:bbad2f109b97a4b3375ad371a5300d42bc9251dad61cd7bc66380cad8501cf94 ghcr.io/github/gh-aw-firewall/squid:0.27.16@sha256:483d6a8086752a02d581d7a42629b741e3f2fa9f3a6a10320590cf881638dad3 ghcr.io/github/gh-aw-mcpg:v0.3.32@sha256:63e46b56dfd70895a701b6fc6dd0189e11e2d875f327f1781e81b31848735477 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.16 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16 ghcr.io/github/gh-aw-firewall/squid:0.27.16 ghcr.io/github/gh-aw-mcpg:v0.3.32@sha256:63e46b56dfd70895a701b6fc6dd0189e11e2d875f327f1781e81b31848735477 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" @@ -788,7 +788,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.16/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"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\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.5\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.16,squid=sha256:483d6a8086752a02d581d7a42629b741e3f2fa9f3a6a10320590cf881638dad3,agent=sha256:94bbf52b74d38e8117387e93e698f79d678dd3879faa0e57f2ea128eda8fb507,api-proxy=sha256:bbad2f109b97a4b3375ad371a5300d42bc9251dad61cd7bc66380cad8501cf94,cli-proxy=sha256:8d83ae45bc1b7a705dc99ef4f76ec782792bbf3561c6923f350b34a68a577bae\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.16/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"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\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.5\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.16\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" @@ -1316,7 +1316,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.16@sha256:94bbf52b74d38e8117387e93e698f79d678dd3879faa0e57f2ea128eda8fb507 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16@sha256:bbad2f109b97a4b3375ad371a5300d42bc9251dad61cd7bc66380cad8501cf94 ghcr.io/github/gh-aw-firewall/squid:0.27.16@sha256:483d6a8086752a02d581d7a42629b741e3f2fa9f3a6a10320590cf881638dad3 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.16 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16 ghcr.io/github/gh-aw-firewall/squid:0.27.16 - name: Check if detection needed id: detection_guard if: always() @@ -1403,7 +1403,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.16/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5},\"container\":{\"imageTag\":\"0.27.16,squid=sha256:483d6a8086752a02d581d7a42629b741e3f2fa9f3a6a10320590cf881638dad3,agent=sha256:94bbf52b74d38e8117387e93e698f79d678dd3879faa0e57f2ea128eda8fb507,api-proxy=sha256:bbad2f109b97a4b3375ad371a5300d42bc9251dad61cd7bc66380cad8501cf94,cli-proxy=sha256:8d83ae45bc1b7a705dc99ef4f76ec782792bbf3561c6923f350b34a68a577bae\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.16/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5},\"container\":{\"imageTag\":\"0.27.16\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" From 6cb36c445cabf5182268ba2889e7abf8f59260b4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Jun 2026 22:58:46 +0000 Subject: [PATCH 2/8] Add pre-flight workflow file check and full-branch allowed_files validation for safe output server Root cause: When a review/fallback branch is pushed as a new ref, GitHub validates the entire commit history for .github/workflows/** changes (not just the new commits). This caused pushes to fail when the PR branch had earlier workflow file commits, even though the current changeset only touched allowed files (e.g. .changeset/*.md). Changes: - pkg/workflow/safe_outputs_handler_registry.go: expose allow_workflows in the push_to_pull_request_branch handler config so the JS handler can read it - actions/setup/js/push_to_pull_request_branch.cjs: add detectWorkflowFileChanges() helper + pre-flight checks before review and fallback branch pushes; when allow_workflows is false (default), fail early with a typed workflows_scope_required error before checkout/push side effects occur - actions/setup/js/safe_outputs_handlers.cjs: add full-branch allowed_files check (origin/baseBranch..branch) before patch generation so the agent receives an actionable error at tool-call time rather than at apply-time - actions/setup/js/push_to_pull_request_branch.test.cjs: add 3 new tests covering the pre-flight check (fires early, skipped when allow_workflows: true) Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../setup/js/push_to_pull_request_branch.cjs | 58 +++++++++++++++ .../js/push_to_pull_request_branch.test.cjs | 71 +++++++++++++++++++ actions/setup/js/safe_outputs_handlers.cjs | 50 +++++++++++++ pkg/workflow/safe_outputs_handler_registry.go | 1 + 4 files changed, 180 insertions(+) diff --git a/actions/setup/js/push_to_pull_request_branch.cjs b/actions/setup/js/push_to_pull_request_branch.cjs index b7f4782171a..5b730af00d2 100644 --- a/actions/setup/js/push_to_pull_request_branch.cjs +++ b/actions/setup/js/push_to_pull_request_branch.cjs @@ -141,6 +141,38 @@ function isWorkflowsScopeRejection(stderr) { * @param {typeof core} core - Actions core logger * @returns {{ success: false, error_type: "workflows_scope_required", error: string }} */ + +/** + * Returns the list of unique workflow file paths (.github/workflows/**) present in the + * local branch history relative to origin/HEAD (the remote default branch). This is + * used as a pre-flight check before pushing a new branch ref: GitHub rejects such pushes + * when the token lacks the 'workflows' scope, even if the current changeset itself does + * not touch workflow files (the rejection is based on ALL commits reachable from the ref). + * + * Falls back to an empty array when origin/HEAD is not resolvable or the git command + * fails, so the caller can treat an empty result as "no workflow changes detected" and + * let the push proceed (where a real failure will still surface the typed error). + * + * @param {{ getExecOutput: Function }} exec - @actions/exec module (or compatible mock) + * @param {Record} gitOptions - Base git exec options (cwd, env, etc.) + * @returns {Promise} Unique workflow file paths found in the branch history + */ +async function detectWorkflowFileChanges(exec, gitOptions) { + try { + const result = await exec.getExecOutput("git", ["log", "--name-only", "--pretty=format:", "HEAD", "--not", "origin/HEAD", "--", ".github/workflows/"], { ...gitOptions, ignoreReturnCode: true }); + return [ + ...new Set( + result.stdout + .split("\n") + .map(f => f.trim()) + .filter(Boolean) + ), + ]; + } catch { + return []; + } +} + function buildWorkflowsScopeError(context, core) { core.error(`${context} push rejected: the branch includes changes to workflow files (.github/workflows/**) that require the 'workflows' scope on the push token.`); core.error("To allow this workflow to push workflow file changes, configure 'push-to-pull-request-branch.allow-workflows: true' together with a GitHub App in 'safe-outputs.github-app'."); @@ -170,6 +202,7 @@ async function main(config = {}) { const commitTitleSuffix = config.commit_title_suffix || ""; const maxSizeKb = parsePositiveInteger(config.max_patch_size) ?? 4096; const maxCount = config.max || 0; // 0 means no limit + const allowWorkflows = config.allow_workflows === true; // Cross-repo support: resolve target repository from config // This allows pushing to PRs in a different repository than the workflow @@ -1041,6 +1074,20 @@ async function main(config = {}) { // normalizeBranchName to enforce valid git ref characters + max length. const reviewBranchName = normalizeBranchName(`${branchName}-review`, String(Date.now())); try { + // Pre-flight: check full branch history for workflow file changes. + // GitHub rejects pushes of new branch refs whose commit history contains + // .github/workflows/** changes when the token lacks the 'workflows' scope — + // even if the current changeset itself does not touch workflow files. + // Failing here avoids leaving the local branch in a renamed state after + // a rejected push, and surfaces the error before any side effects. + if (!allowWorkflows) { + const workflowFiles = await detectWorkflowFileChanges(exec, baseGitOpts); + if (workflowFiles.length > 0) { + core.info(`Pre-flight check: branch history contains workflow file changes (${workflowFiles.join(", ")}). Failing before push attempt.`); + return buildWorkflowsScopeError("Review branch pre-flight", core); + } + } + // Rename current local branch to review branch await exec.exec("git", ["checkout", "-b", reviewBranchName], baseGitOpts); core.info(`Created review branch: ${reviewBranchName}`); @@ -1210,6 +1257,17 @@ async function main(config = {}) { const fallbackBranchName = normalizeBranchName(`${branchName}-fallback`, String(Date.now())); core.warning(`Non-fast-forward push detected; creating fallback pull request from '${fallbackBranchName}' to '${branchName}'`); try { + // Pre-flight: check full branch history for workflow file changes. + // Like the review branch path, creating a new fallback branch ref triggers + // GitHub's scope check on the full commit history, not just the new commits. + if (!allowWorkflows) { + const workflowFiles = await detectWorkflowFileChanges(exec, baseGitOpts); + if (workflowFiles.length > 0) { + core.info(`Pre-flight check: branch history contains workflow file changes (${workflowFiles.join(", ")}). Failing before push attempt.`); + return buildWorkflowsScopeError("Fallback branch pre-flight", core); + } + } + await exec.exec("git", ["checkout", "-b", fallbackBranchName], baseGitOpts); // Use getExecOutput to capture stderr for 'workflows' scope diagnostics const fallbackPushOutput = await exec.getExecOutput("git", ["push", "origin", fallbackBranchName], { diff --git a/actions/setup/js/push_to_pull_request_branch.test.cjs b/actions/setup/js/push_to_pull_request_branch.test.cjs index 32d2505f9e0..38b18cc2c2d 100644 --- a/actions/setup/js/push_to_pull_request_branch.test.cjs +++ b/actions/setup/js/push_to_pull_request_branch.test.cjs @@ -1110,6 +1110,11 @@ index 0000000..abc1234 return { exitCode: 0, stdout: "1111111111111111111111111111111111111111\trefs/heads/feature-branch\n", stderr: "" }; } if (argList[0] === "log") { + // Pre-flight workflow check uses --not origin/HEAD; return empty to avoid + // short-circuiting the fallback path with a workflows_scope_required error. + if (argList.includes("origin/HEAD")) { + return { exitCode: 0, stdout: "", stderr: "" }; + } return { exitCode: 0, stdout: "Test commit\n", stderr: "" }; } if (argList[0] === "diff-tree") { @@ -1440,6 +1445,72 @@ index 0000000..abc1234 expect(result.error_type).toBeUndefined(); expect(result.error).toContain("Failed to create review PR"); }); + + it("should fail pre-flight with workflows_scope_required when branch history contains workflow files", async () => { + process.env.GH_AW_DETECTION_CONCLUSION = "warning"; + createPatchFile("review-branch-preflight-workflow-files"); + + const originalGetExecOutput = mockExec.getExecOutput; + mockExec.getExecOutput = vi.fn().mockImplementation(async (cmd, args, options) => { + const argList = Array.isArray(args) ? args : []; + // Pre-flight git log returns a workflow file path, simulating branch history + // that contains .github/workflows/** changes from earlier commits. + if (cmd === "git" && argList[0] === "log" && argList.includes("--not") && argList.includes("origin/HEAD")) { + return { exitCode: 0, stdout: ".github/workflows/ci.yml\n", stderr: "" }; + } + // The git push should NOT be reached — pre-flight check fires first + if (cmd === "git" && argList[0] === "push" && argList[1] === "origin") { + throw new Error("git push should not be called when pre-flight check fires"); + } + return originalGetExecOutput(cmd, args, options); + }); + + const module = await loadModule(); + // allow_workflows not set (default false) — pre-flight check is active + const handler = await module.main({}); + const result = await handler({ branch: "review-branch-preflight-workflow-files" }, {}); + + expect(result.success).toBe(false); + expect(result.error_type).toBe("workflows_scope_required"); + expect(result.error).toContain("'workflows' scope"); + expect(result.error).toContain("allow-workflows"); + // Pre-flight fires before checkout — no "Failed to create review PR" message + const errorCalls = mockCore.error.mock.calls.map(c => c[0]); + expect(errorCalls.some(msg => msg.includes("Failed to create review PR"))).toBe(false); + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Pre-flight check")); + }); + + it("should skip pre-flight check and attempt push when allow_workflows is true", async () => { + process.env.GH_AW_DETECTION_CONCLUSION = "warning"; + createPatchFile("review-branch-allow-workflows-skip-preflight"); + + let preflightCalled = false; + let pushCalled = false; + const originalGetExecOutput = mockExec.getExecOutput; + mockExec.getExecOutput = vi.fn().mockImplementation(async (cmd, args, options) => { + const argList = Array.isArray(args) ? args : []; + if (cmd === "git" && argList[0] === "log" && argList.includes("--not") && argList.includes("origin/HEAD")) { + preflightCalled = true; + return { exitCode: 0, stdout: ".github/workflows/ci.yml\n", stderr: "" }; + } + if (cmd === "git" && argList[0] === "push" && argList[1] === "origin") { + pushCalled = true; + return { exitCode: 0, stdout: "", stderr: "" }; + } + return originalGetExecOutput(cmd, args, options); + }); + + const module = await loadModule(); + // allow_workflows: true — skip the pre-flight check + const handler = await module.main({ allow_workflows: true }); + const result = await handler({ branch: "review-branch-allow-workflows-skip-preflight" }, {}); + + // Pre-flight check should NOT have run + expect(preflightCalled).toBe(false); + // Push should have been attempted + expect(pushCalled).toBe(true); + expect(result.success).toBe(true); + }); }); // ────────────────────────────────────────────────────── diff --git a/actions/setup/js/safe_outputs_handlers.cjs b/actions/setup/js/safe_outputs_handlers.cjs index bd3ae78c668..80d86519fda 100644 --- a/actions/setup/js/safe_outputs_handlers.cjs +++ b/actions/setup/js/safe_outputs_handlers.cjs @@ -1250,6 +1250,56 @@ function createHandlers(server, appendSafeOutput, config = {}) { pushPinnedSha = null; } + // Full-branch allowed_files check: validate that ALL commits on the PR branch + // (relative to origin/baseBranch) only touch files permitted by allowed_files. + // The incremental patch check at apply-time only inspects the net diff between + // origin/ and the local branch tip; this catches disallowed files that + // appear in earlier commits on the branch (e.g. a Copilot branch that also + // modified .github/workflows/**) and returns an actionable error to the agent + // before any transport artifacts are generated. + if (Array.isArray(pushConfig.allowed_files) && pushConfig.allowed_files.length > 0) { + try { + const branchHistoryFiles = execGitSync(["log", "--name-only", "--pretty=format:", `origin/${baseBranch}..${entry.branch}`], { cwd: pushGitCwd }) + .toString() + .split("\n") + .map(s => s.trim()) + .filter(Boolean); + + if (branchHistoryFiles.length > 0) { + const allowedPatterns = pushConfig.allowed_files.map(p => globPatternToRegex(p)); + const excludedPatterns = Array.isArray(pushConfig.excluded_files) ? pushConfig.excluded_files.map(p => globPatternToRegex(p)) : []; + const uniqueFiles = [...new Set(branchHistoryFiles)]; + const disallowedFiles = uniqueFiles.filter(f => !allowedPatterns.some(re => re.test(f)) && !excludedPatterns.some(re => re.test(f))); + + if (disallowedFiles.length > 0) { + const sample = disallowedFiles.slice(0, 5); + const remaining = disallowedFiles.length - sample.length; + const filesStr = remaining > 0 ? `${sample.join(", ")} (+${remaining} more)` : sample.join(", "); + server.debug(`Full-branch allowed-files check failed: ${filesStr}`); + return { + content: [ + { + type: "text", + text: JSON.stringify({ + result: "error", + error: `Cannot push to pull request branch: the branch '${entry.branch}' history contains commits that modify files outside the allowed-files configuration: ${filesStr}. Remove the disallowed file changes from your commits and retry, or update the allowed-files configuration to include these files.`, + disallowed_files: disallowedFiles, + }), + }, + ], + isError: true, + }; + } + } + } catch (fullBranchCheckError) { + // Non-fatal: if origin/baseBranch is not available locally or git fails, + // skip the full-branch check and continue. The apply-time policy check in + // push_to_pull_request_branch.cjs will still enforce allowed_files against + // the incremental patch content. + server.debug(`Full-branch allowed-files check skipped (non-fatal): ${getErrorMessage(fullBranchCheckError)}`); + } + } + // Always generate an incremental patch for policy enforcement (allowed-files/protected-files/excluded-files), // even when bundle transport is selected for apply-time commit transport. server.debug(`Generating incremental patch for push_to_pull_request_branch with branch: ${entry.branch}, baseBranch: ${baseBranch}`); diff --git a/pkg/workflow/safe_outputs_handler_registry.go b/pkg/workflow/safe_outputs_handler_registry.go index c4c76781d04..8bbbffc89e0 100644 --- a/pkg/workflow/safe_outputs_handler_registry.go +++ b/pkg/workflow/safe_outputs_handler_registry.go @@ -557,6 +557,7 @@ var handlerRegistry = map[string]handlerBuilder{ AddBoolPtr("fallback_as_pull_request", c.FallbackAsPullRequest). AddBoolPtr("signed_commits", c.SignedCommits). AddBoolPtr("check_branch_protection", c.CheckBranchProtection). + AddIfTrue("allow_workflows", c.AllowWorkflows). Build() }, "update_pull_request": func(cfg *SafeOutputsConfig) map[string]any { From c1d768d8d0f7c6b0397ee8deabffa44dfeff3632 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Jun 2026 23:03:42 +0000 Subject: [PATCH 3/8] Address review feedback: add clarifying comments for excluded_files logic and origin/HEAD baseline Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../setup/js/push_to_pull_request_branch.cjs | 17 ++++++++++------- actions/setup/js/safe_outputs_handlers.cjs | 3 +++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/actions/setup/js/push_to_pull_request_branch.cjs b/actions/setup/js/push_to_pull_request_branch.cjs index 5b730af00d2..112d9541bdf 100644 --- a/actions/setup/js/push_to_pull_request_branch.cjs +++ b/actions/setup/js/push_to_pull_request_branch.cjs @@ -144,14 +144,17 @@ function isWorkflowsScopeRejection(stderr) { /** * Returns the list of unique workflow file paths (.github/workflows/**) present in the - * local branch history relative to origin/HEAD (the remote default branch). This is - * used as a pre-flight check before pushing a new branch ref: GitHub rejects such pushes - * when the token lacks the 'workflows' scope, even if the current changeset itself does - * not touch workflow files (the rejection is based on ALL commits reachable from the ref). + * local branch history relative to `origin/HEAD` (the remote default branch as set by + * `actions/checkout`). This is used as a pre-flight check before pushing a new branch + * ref: GitHub rejects such pushes when the token lacks the 'workflows' scope, even if + * the current changeset itself does not touch workflow files (the rejection is based on + * ALL commits reachable from the pushed ref). * - * Falls back to an empty array when origin/HEAD is not resolvable or the git command - * fails, so the caller can treat an empty result as "no workflow changes detected" and - * let the push proceed (where a real failure will still surface the typed error). + * Uses `origin/HEAD` as the exclusion baseline because it is reliably set by + * `actions/checkout` and represents commits that GitHub has already accepted. Falls back + * to an empty array (no workflow changes detected) when `origin/HEAD` is not resolvable + * or the git command fails — in that case the push is still attempted, and any real + * 'workflows' scope rejection will be caught and surfaced as the typed error downstream. * * @param {{ getExecOutput: Function }} exec - @actions/exec module (or compatible mock) * @param {Record} gitOptions - Base git exec options (cwd, env, etc.) diff --git a/actions/setup/js/safe_outputs_handlers.cjs b/actions/setup/js/safe_outputs_handlers.cjs index 80d86519fda..1e151ccc924 100644 --- a/actions/setup/js/safe_outputs_handlers.cjs +++ b/actions/setup/js/safe_outputs_handlers.cjs @@ -1267,6 +1267,9 @@ function createHandlers(server, appendSafeOutput, config = {}) { if (branchHistoryFiles.length > 0) { const allowedPatterns = pushConfig.allowed_files.map(p => globPatternToRegex(p)); + // Files matching excluded_files are intentionally exempt: they will be stripped + // from the patch at generation time via :(exclude) pathspecs, so they won't be + // present in the final changeset applied to the branch. const excludedPatterns = Array.isArray(pushConfig.excluded_files) ? pushConfig.excluded_files.map(p => globPatternToRegex(p)) : []; const uniqueFiles = [...new Set(branchHistoryFiles)]; const disallowedFiles = uniqueFiles.filter(f => !allowedPatterns.some(re => re.test(f)) && !excludedPatterns.some(re => re.test(f))); From 6d0073ca3bbdc322e7d1f800d3400dc872053293 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Jul 2026 03:20:49 +0000 Subject: [PATCH 4/8] fix: correct test brace imbalance and tighten allowed-files assertion strings Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/daily-team-status.lock.yml | 16 +- .../setup/js/push_to_pull_request_branch.cjs | 109 ++++++---- .../js/push_to_pull_request_branch.test.cjs | 12 +- actions/setup/js/safe_outputs_handlers.cjs | 2 +- .../setup/js/safe_outputs_handlers.test.cjs | 202 ++++++++++++++++++ 5 files changed, 289 insertions(+), 52 deletions(-) diff --git a/.github/workflows/daily-team-status.lock.yml b/.github/workflows/daily-team-status.lock.yml index e48da18cca8..12b2c936b1f 100644 --- a/.github/workflows/daily-team-status.lock.yml +++ b/.github/workflows/daily-team-status.lock.yml @@ -1,5 +1,5 @@ # gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"9f61ffba0d4e9663a43f41030c13a4c7c71ebac8122f586cbd706756a605df94","body_hash":"33c10cc22b8836b79387efda582e48c5a463e9849880a01d58704a0fa291e986","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","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":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.16"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.16"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.32","digest":"sha256:63e46b56dfd70895a701b6fc6dd0189e11e2d875f327f1781e81b31848735477","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.32@sha256:63e46b56dfd70895a701b6fc6dd0189e11e2d875f327f1781e81b31848735477"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.5.0","digest":"sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4","pinned_image":"ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4"}]} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","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":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.16","digest":"sha256:94bbf52b74d38e8117387e93e698f79d678dd3879faa0e57f2ea128eda8fb507","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.16@sha256:94bbf52b74d38e8117387e93e698f79d678dd3879faa0e57f2ea128eda8fb507"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16","digest":"sha256:bbad2f109b97a4b3375ad371a5300d42bc9251dad61cd7bc66380cad8501cf94","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16@sha256:bbad2f109b97a4b3375ad371a5300d42bc9251dad61cd7bc66380cad8501cf94"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.16","digest":"sha256:483d6a8086752a02d581d7a42629b741e3f2fa9f3a6a10320590cf881638dad3","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.16@sha256:483d6a8086752a02d581d7a42629b741e3f2fa9f3a6a10320590cf881638dad3"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.32","digest":"sha256:63e46b56dfd70895a701b6fc6dd0189e11e2d875f327f1781e81b31848735477","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.32@sha256:63e46b56dfd70895a701b6fc6dd0189e11e2d875f327f1781e81b31848735477"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.5.0","digest":"sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4","pinned_image":"ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4"}]} # 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 # # ___ _ _ @@ -47,9 +47,9 @@ # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.27.16 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16 -# - ghcr.io/github/gh-aw-firewall/squid:0.27.16 +# - ghcr.io/github/gh-aw-firewall/agent:0.27.16@sha256:94bbf52b74d38e8117387e93e698f79d678dd3879faa0e57f2ea128eda8fb507 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16@sha256:bbad2f109b97a4b3375ad371a5300d42bc9251dad61cd7bc66380cad8501cf94 +# - ghcr.io/github/gh-aw-firewall/squid:0.27.16@sha256:483d6a8086752a02d581d7a42629b741e3f2fa9f3a6a10320590cf881638dad3 # - ghcr.io/github/gh-aw-mcpg:v0.3.32@sha256:63e46b56dfd70895a701b6fc6dd0189e11e2d875f327f1781e81b31848735477 # - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b # - ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 @@ -507,7 +507,7 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.16 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16 ghcr.io/github/gh-aw-firewall/squid:0.27.16 ghcr.io/github/gh-aw-mcpg:v0.3.32@sha256:63e46b56dfd70895a701b6fc6dd0189e11e2d875f327f1781e81b31848735477 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.16@sha256:94bbf52b74d38e8117387e93e698f79d678dd3879faa0e57f2ea128eda8fb507 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16@sha256:bbad2f109b97a4b3375ad371a5300d42bc9251dad61cd7bc66380cad8501cf94 ghcr.io/github/gh-aw-firewall/squid:0.27.16@sha256:483d6a8086752a02d581d7a42629b741e3f2fa9f3a6a10320590cf881638dad3 ghcr.io/github/gh-aw-mcpg:v0.3.32@sha256:63e46b56dfd70895a701b6fc6dd0189e11e2d875f327f1781e81b31848735477 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" @@ -788,7 +788,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.16/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"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\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.5\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.16\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.16/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"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\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.5\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.16,squid=sha256:483d6a8086752a02d581d7a42629b741e3f2fa9f3a6a10320590cf881638dad3,agent=sha256:94bbf52b74d38e8117387e93e698f79d678dd3879faa0e57f2ea128eda8fb507,api-proxy=sha256:bbad2f109b97a4b3375ad371a5300d42bc9251dad61cd7bc66380cad8501cf94,cli-proxy=sha256:8d83ae45bc1b7a705dc99ef4f76ec782792bbf3561c6923f350b34a68a577bae\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" @@ -1316,7 +1316,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.16 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16 ghcr.io/github/gh-aw-firewall/squid:0.27.16 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.16@sha256:94bbf52b74d38e8117387e93e698f79d678dd3879faa0e57f2ea128eda8fb507 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16@sha256:bbad2f109b97a4b3375ad371a5300d42bc9251dad61cd7bc66380cad8501cf94 ghcr.io/github/gh-aw-firewall/squid:0.27.16@sha256:483d6a8086752a02d581d7a42629b741e3f2fa9f3a6a10320590cf881638dad3 - name: Check if detection needed id: detection_guard if: always() @@ -1403,7 +1403,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.16/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5},\"container\":{\"imageTag\":\"0.27.16\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.16/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5},\"container\":{\"imageTag\":\"0.27.16,squid=sha256:483d6a8086752a02d581d7a42629b741e3f2fa9f3a6a10320590cf881638dad3,agent=sha256:94bbf52b74d38e8117387e93e698f79d678dd3879faa0e57f2ea128eda8fb507,api-proxy=sha256:bbad2f109b97a4b3375ad371a5300d42bc9251dad61cd7bc66380cad8501cf94,cli-proxy=sha256:8d83ae45bc1b7a705dc99ef4f76ec782792bbf3561c6923f350b34a68a577bae\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" diff --git a/actions/setup/js/push_to_pull_request_branch.cjs b/actions/setup/js/push_to_pull_request_branch.cjs index 112d9541bdf..ffd43add66b 100644 --- a/actions/setup/js/push_to_pull_request_branch.cjs +++ b/actions/setup/js/push_to_pull_request_branch.cjs @@ -133,36 +133,42 @@ function isWorkflowsScopeRejection(stderr) { return lower.includes("`workflows` scope") || lower.includes("workflow can be created or updated due to timeout"); } -/** - * Builds the typed result and logs actionable guidance when a branch push fails - * because the token lacks the 'workflows' scope. - * - * @param {string} context - Short label identifying the push path (e.g. "Review branch", "Fallback branch") - * @param {typeof core} core - Actions core logger - * @returns {{ success: false, error_type: "workflows_scope_required", error: string }} - */ - /** * Returns the list of unique workflow file paths (.github/workflows/**) present in the - * local branch history relative to `origin/HEAD` (the remote default branch as set by - * `actions/checkout`). This is used as a pre-flight check before pushing a new branch - * ref: GitHub rejects such pushes when the token lacks the 'workflows' scope, even if - * the current changeset itself does not touch workflow files (the rejection is based on - * ALL commits reachable from the pushed ref). + * local branch history beyond the PR's base branch. This is used as a pre-flight check + * before pushing a new branch ref: GitHub rejects such pushes when the token lacks the + * 'workflows' scope, even if the current changeset itself does not touch workflow files + * (the rejection is based on ALL commits reachable from the pushed ref). * - * Uses `origin/HEAD` as the exclusion baseline because it is reliably set by - * `actions/checkout` and represents commits that GitHub has already accepted. Falls back - * to an empty array (no workflow changes detected) when `origin/HEAD` is not resolvable - * or the git command fails — in that case the push is still attempted, and any real - * 'workflows' scope rejection will be caught and surfaced as the typed error downstream. + * Uses `origin/${baseBranch}` as the exclusion baseline so that commits already on the + * PR's target branch (which GitHub has already accepted) are excluded. Falls back to + * `origin/HEAD` when `baseBranch` is not available, and to an empty array (no workflow + * changes detected) when the baseline ref is not resolvable or the git command fails — + * in that case the push is still attempted and any real 'workflows' scope rejection will + * be caught and surfaced as the typed error downstream. + * + * Note: `origin/${baseBranch}` and `origin/HEAD` are intentionally different baselines + * for their respective layers. `origin/${baseBranch}` limits detection to commits the + * agent actually introduced (correct for the PR delta). Using `origin/HEAD` here would + * traverse commits on the target branch itself for PRs targeting non-default branches, + * producing false-positive `workflows_scope_required` errors. * * @param {{ getExecOutput: Function }} exec - @actions/exec module (or compatible mock) * @param {Record} gitOptions - Base git exec options (cwd, env, etc.) + * @param {string | undefined} baseBranch - PR base branch name (e.g. "main"); falls back to origin/HEAD when not provided * @returns {Promise} Unique workflow file paths found in the branch history */ -async function detectWorkflowFileChanges(exec, gitOptions) { +async function detectWorkflowFileChanges(exec, gitOptions, baseBranch) { + const baseline = baseBranch ? `origin/${baseBranch}` : "origin/HEAD"; try { - const result = await exec.getExecOutput("git", ["log", "--name-only", "--pretty=format:", "HEAD", "--not", "origin/HEAD", "--", ".github/workflows/"], { ...gitOptions, ignoreReturnCode: true }); + const result = await exec.getExecOutput("git", ["log", "--name-only", "--pretty=format:", "HEAD", "--not", baseline, "--", ".github/workflows/"], { ...gitOptions, ignoreReturnCode: true }); + if (result.exitCode !== 0) { + // Non-zero exit means the baseline ref was not resolvable or git failed; + // treat as no workflow changes so the push proceeds and any real scope + // rejection surfaces downstream. + core.debug(`detectWorkflowFileChanges: git log exited ${result.exitCode} (baseline '${baseline}' may be unavailable); skipping pre-flight`); + return []; + } return [ ...new Set( result.stdout @@ -171,14 +177,49 @@ async function detectWorkflowFileChanges(exec, gitOptions) { .filter(Boolean) ), ]; - } catch { + } catch (err) { + core.debug(`detectWorkflowFileChanges: git log threw (baseline '${baseline}'); skipping pre-flight: ${err instanceof Error ? err.message : String(err)}`); return []; } } -function buildWorkflowsScopeError(context, core) { - core.error(`${context} push rejected: the branch includes changes to workflow files (.github/workflows/**) that require the 'workflows' scope on the push token.`); - core.error("To allow this workflow to push workflow file changes, configure 'push-to-pull-request-branch.allow-workflows: true' together with a GitHub App in 'safe-outputs.github-app'."); +/** + * Performs a pre-flight workflow-scope check before pushing a new branch ref. + * Returns the typed error object when the branch history contains workflow file changes + * and `allowWorkflows` is false; returns null when the push may proceed. + * + * Extracts the duplicated guard that appears in both the review-branch and + * fallback-branch push paths so future changes only need to be made in one place. + * + * @param {{ getExecOutput: Function }} exec - @actions/exec module (or compatible mock) + * @param {Record} gitOptions - Base git exec options (cwd, env, etc.) + * @param {boolean} allowWorkflows - Whether the push token has the 'workflows' scope + * @param {string | undefined} baseBranch - PR base branch name passed through to detectWorkflowFileChanges + * @param {string} context - Short label for the push path (e.g. "Review branch", "Fallback branch") + * @param {typeof core} coreLogger - Actions core logger + * @returns {Promise<{ success: false, error_type: string, error: string } | null>} + */ +async function runWorkflowScopePreflightCheck(exec, gitOptions, allowWorkflows, baseBranch, context, coreLogger) { + if (allowWorkflows) return null; + const workflowFiles = await detectWorkflowFileChanges(exec, gitOptions, baseBranch); + if (workflowFiles.length > 0) { + coreLogger.info(`Pre-flight check: branch history contains workflow file changes (${workflowFiles.join(", ")}). Failing before push attempt.`); + return buildWorkflowsScopeError(`${context} pre-flight`, coreLogger); + } + return null; +} + +/** + * Builds the typed result and logs actionable guidance when a branch push fails + * because the token lacks the 'workflows' scope. + * + * @param {string} context - Short label identifying the push path (e.g. "Review branch", "Fallback branch") + * @param {typeof core} coreLogger - Actions core logger + * @returns {{ success: false, error_type: "workflows_scope_required", error: string }} + */ +function buildWorkflowsScopeError(context, coreLogger) { + coreLogger.error(`${context} push rejected: the branch includes changes to workflow files (.github/workflows/**) that require the 'workflows' scope on the push token.`); + coreLogger.error("To allow this workflow to push workflow file changes, configure 'push-to-pull-request-branch.allow-workflows: true' together with a GitHub App in 'safe-outputs.github-app'."); return { success: false, error_type: "workflows_scope_required", @@ -1083,12 +1124,9 @@ async function main(config = {}) { // even if the current changeset itself does not touch workflow files. // Failing here avoids leaving the local branch in a renamed state after // a rejected push, and surfaces the error before any side effects. - if (!allowWorkflows) { - const workflowFiles = await detectWorkflowFileChanges(exec, baseGitOpts); - if (workflowFiles.length > 0) { - core.info(`Pre-flight check: branch history contains workflow file changes (${workflowFiles.join(", ")}). Failing before push attempt.`); - return buildWorkflowsScopeError("Review branch pre-flight", core); - } + { + const preflightError = await runWorkflowScopePreflightCheck(exec, baseGitOpts, allowWorkflows, pullRequest?.base?.ref, "Review branch", core); + if (preflightError) return preflightError; } // Rename current local branch to review branch @@ -1263,12 +1301,9 @@ async function main(config = {}) { // Pre-flight: check full branch history for workflow file changes. // Like the review branch path, creating a new fallback branch ref triggers // GitHub's scope check on the full commit history, not just the new commits. - if (!allowWorkflows) { - const workflowFiles = await detectWorkflowFileChanges(exec, baseGitOpts); - if (workflowFiles.length > 0) { - core.info(`Pre-flight check: branch history contains workflow file changes (${workflowFiles.join(", ")}). Failing before push attempt.`); - return buildWorkflowsScopeError("Fallback branch pre-flight", core); - } + { + const preflightError = await runWorkflowScopePreflightCheck(exec, baseGitOpts, allowWorkflows, pullRequest?.base?.ref, "Fallback branch", core); + if (preflightError) return preflightError; } await exec.exec("git", ["checkout", "-b", fallbackBranchName], baseGitOpts); diff --git a/actions/setup/js/push_to_pull_request_branch.test.cjs b/actions/setup/js/push_to_pull_request_branch.test.cjs index 38b18cc2c2d..89bdfb44e87 100644 --- a/actions/setup/js/push_to_pull_request_branch.test.cjs +++ b/actions/setup/js/push_to_pull_request_branch.test.cjs @@ -1110,9 +1110,9 @@ index 0000000..abc1234 return { exitCode: 0, stdout: "1111111111111111111111111111111111111111\trefs/heads/feature-branch\n", stderr: "" }; } if (argList[0] === "log") { - // Pre-flight workflow check uses --not origin/HEAD; return empty to avoid + // Pre-flight workflow check targets .github/workflows/; return empty to avoid // short-circuiting the fallback path with a workflows_scope_required error. - if (argList.includes("origin/HEAD")) { + if (argList.includes(".github/workflows/")) { return { exitCode: 0, stdout: "", stderr: "" }; } return { exitCode: 0, stdout: "Test commit\n", stderr: "" }; @@ -1453,9 +1453,9 @@ index 0000000..abc1234 const originalGetExecOutput = mockExec.getExecOutput; mockExec.getExecOutput = vi.fn().mockImplementation(async (cmd, args, options) => { const argList = Array.isArray(args) ? args : []; - // Pre-flight git log returns a workflow file path, simulating branch history - // that contains .github/workflows/** changes from earlier commits. - if (cmd === "git" && argList[0] === "log" && argList.includes("--not") && argList.includes("origin/HEAD")) { + // Pre-flight git log targets .github/workflows/ directory — returns a workflow + // file path to simulate branch history containing .github/workflows/** changes. + if (cmd === "git" && argList[0] === "log" && argList.includes(".github/workflows/")) { return { exitCode: 0, stdout: ".github/workflows/ci.yml\n", stderr: "" }; } // The git push should NOT be reached — pre-flight check fires first @@ -1489,7 +1489,7 @@ index 0000000..abc1234 const originalGetExecOutput = mockExec.getExecOutput; mockExec.getExecOutput = vi.fn().mockImplementation(async (cmd, args, options) => { const argList = Array.isArray(args) ? args : []; - if (cmd === "git" && argList[0] === "log" && argList.includes("--not") && argList.includes("origin/HEAD")) { + if (cmd === "git" && argList[0] === "log" && argList.includes(".github/workflows/")) { preflightCalled = true; return { exitCode: 0, stdout: ".github/workflows/ci.yml\n", stderr: "" }; } diff --git a/actions/setup/js/safe_outputs_handlers.cjs b/actions/setup/js/safe_outputs_handlers.cjs index 1e151ccc924..91ca07c790a 100644 --- a/actions/setup/js/safe_outputs_handlers.cjs +++ b/actions/setup/js/safe_outputs_handlers.cjs @@ -1259,7 +1259,7 @@ function createHandlers(server, appendSafeOutput, config = {}) { // before any transport artifacts are generated. if (Array.isArray(pushConfig.allowed_files) && pushConfig.allowed_files.length > 0) { try { - const branchHistoryFiles = execGitSync(["log", "--name-only", "--pretty=format:", `origin/${baseBranch}..${entry.branch}`], { cwd: pushGitCwd }) + const branchHistoryFiles = execGitSync(["log", "--name-only", "--pretty=format:", `origin/${baseBranch}..${pushPinnedSha ?? entry.branch}`, "--"], { cwd: pushGitCwd }) .toString() .split("\n") .map(s => s.trim()) diff --git a/actions/setup/js/safe_outputs_handlers.test.cjs b/actions/setup/js/safe_outputs_handlers.test.cjs index df15d908ffa..39bcd7e62f8 100644 --- a/actions/setup/js/safe_outputs_handlers.test.cjs +++ b/actions/setup/js/safe_outputs_handlers.test.cjs @@ -1800,6 +1800,208 @@ describe("safe_outputs_handlers", () => { process.env.GITHUB_REF_NAME = "feature-branch"; // restore for sibling tests } }); + + describe("full-branch allowed_files check", () => { + /** + * Creates a git repo whose history contains a file that violates `allowed_files` + * (a .js file when only .md is allowed). Sets up `origin/main` as the tracking ref + * so `execGitSync` can compute the range `origin/main..`. + */ + function createRepoWithDisallowedHistoryFile() { + const repoDir = path.join(testWorkspaceDir, "allowed-files-repo"); + fs.mkdirSync(repoDir, { recursive: true }); + + execSync("git init -b main", { cwd: repoDir, stdio: "pipe" }); + execSync("git config user.email 'test@example.com'", { cwd: repoDir, stdio: "pipe" }); + execSync("git config user.name 'Test User'", { cwd: repoDir, stdio: "pipe" }); + + // Base commit on main + fs.writeFileSync(path.join(repoDir, "README.md"), "base\n"); + execSync("git add README.md", { cwd: repoDir, stdio: "pipe" }); + execSync("git commit -m 'base commit'", { cwd: repoDir, stdio: "pipe" }); + const mainSha = execSync("git rev-parse HEAD", { cwd: repoDir, stdio: "pipe" }).toString().trim(); + + // Create feature branch + execSync("git checkout -b feature/work", { cwd: repoDir, stdio: "pipe" }); + + // Commit an .md file (allowed) + fs.writeFileSync(path.join(repoDir, "notes.md"), "notes\n"); + execSync("git add notes.md", { cwd: repoDir, stdio: "pipe" }); + execSync("git commit -m 'add notes'", { cwd: repoDir, stdio: "pipe" }); + + // Commit a .js file (disallowed when allowed_files is ["*.md"]) + fs.writeFileSync(path.join(repoDir, "script.js"), "console.log('hi');\n"); + execSync("git add script.js", { cwd: repoDir, stdio: "pipe" }); + execSync("git commit -m 'add script'", { cwd: repoDir, stdio: "pipe" }); + + // Set up origin/main tracking ref + execSync("git remote add origin https://github.com/test-owner/test-repo.git", { cwd: repoDir, stdio: "pipe" }); + execSync(`git update-ref refs/remotes/origin/main ${mainSha}`, { cwd: repoDir, stdio: "pipe" }); + + return { repoDir }; + } + + it("returns isError when branch history contains a file outside allowed_files", async () => { + let repoDir; + try { + ({ repoDir } = createRepoWithDisallowedHistoryFile()); + } catch { + // Skip if git not available in test environment + return; + } + + process.env.GITHUB_BASE_REF = "main"; + process.env.GITHUB_WORKSPACE = repoDir; + const localHandlers = createHandlers(mockServer, mockAppendSafeOutput, { + push_to_pull_request_branch: { + allowed_files: ["*.md"], + }, + }); + + try { + const result = await localHandlers.pushToPullRequestBranchHandler({ branch: "feature/work" }); + + expect(result.isError).toBe(true); + const data = JSON.parse(result.content[0].text); + expect(data.result).toBe("error"); + expect(data.error).toContain("allowed-files"); + expect(data.disallowed_files).toBeDefined(); + expect(data.disallowed_files).toContain("script.js"); + // Safe output must NOT have been recorded for a disallowed branch + expect(mockAppendSafeOutput).not.toHaveBeenCalled(); + } finally { + delete process.env.GITHUB_BASE_REF; + process.env.GITHUB_WORKSPACE = testWorkspaceDir; + } + }); + + it("does not block when all branch history files are within allowed_files", async () => { + const repoDir = path.join(testWorkspaceDir, "allowed-files-ok-repo"); + fs.mkdirSync(repoDir, { recursive: true }); + try { + execSync("git init -b main", { cwd: repoDir, stdio: "pipe" }); + execSync("git config user.email 'test@example.com'", { cwd: repoDir, stdio: "pipe" }); + execSync("git config user.name 'Test User'", { cwd: repoDir, stdio: "pipe" }); + + fs.writeFileSync(path.join(repoDir, "README.md"), "base\n"); + execSync("git add README.md", { cwd: repoDir, stdio: "pipe" }); + execSync("git commit -m 'base commit'", { cwd: repoDir, stdio: "pipe" }); + const mainSha = execSync("git rev-parse HEAD", { cwd: repoDir, stdio: "pipe" }).toString().trim(); + + execSync("git checkout -b feature/docs-only", { cwd: repoDir, stdio: "pipe" }); + fs.writeFileSync(path.join(repoDir, "CHANGELOG.md"), "## v1.0\n"); + execSync("git add CHANGELOG.md", { cwd: repoDir, stdio: "pipe" }); + execSync("git commit -m 'add changelog'", { cwd: repoDir, stdio: "pipe" }); + + execSync("git remote add origin https://github.com/test-owner/test-repo.git", { cwd: repoDir, stdio: "pipe" }); + execSync(`git update-ref refs/remotes/origin/main ${mainSha}`, { cwd: repoDir, stdio: "pipe" }); + } catch { + return; // Skip if git not available + } + + process.env.GITHUB_BASE_REF = "main"; + process.env.GITHUB_WORKSPACE = repoDir; + const localHandlers = createHandlers(mockServer, mockAppendSafeOutput, { + push_to_pull_request_branch: { + allowed_files: ["*.md"], + }, + }); + + try { + const result = await localHandlers.pushToPullRequestBranchHandler({ branch: "feature/docs-only" }); + + // The allowed_files check should pass; any subsequent failure is unrelated to it + // (e.g. missing patch file in the test environment is acceptable). + if (result.isError) { + const data = JSON.parse(result.content[0].text); + // Must NOT be an allowed_files error + expect(data.error).not.toContain("allowed-files configuration"); + expect(data.disallowed_files).toBeUndefined(); + } + } finally { + delete process.env.GITHUB_BASE_REF; + process.env.GITHUB_WORKSPACE = testWorkspaceDir; + } + }); + + it("exempts files matching excluded_files from the allowed_files check", async () => { + let repoDir; + try { + ({ repoDir } = createRepoWithDisallowedHistoryFile()); + } catch { + return; + } + + process.env.GITHUB_BASE_REF = "main"; + process.env.GITHUB_WORKSPACE = repoDir; + // excluded_files exempts .js files, so script.js should not trigger the error + const localHandlers = createHandlers(mockServer, mockAppendSafeOutput, { + push_to_pull_request_branch: { + allowed_files: ["*.md"], + excluded_files: ["*.js"], + }, + }); + + try { + const result = await localHandlers.pushToPullRequestBranchHandler({ branch: "feature/work" }); + + // .js file is exempted by excluded_files; the check should not block on it + if (result.isError) { + const data = JSON.parse(result.content[0].text); + expect(data.error).not.toContain("allowed-files configuration"); + expect(data.disallowed_files).toBeUndefined(); + } + } finally { + delete process.env.GITHUB_BASE_REF; + process.env.GITHUB_WORKSPACE = testWorkspaceDir; + } + }); + + it("is non-fatal when origin/baseBranch is not available (continues without blocking)", async () => { + const repoDir = path.join(testWorkspaceDir, "no-origin-repo"); + fs.mkdirSync(repoDir, { recursive: true }); + try { + execSync("git init -b main", { cwd: repoDir, stdio: "pipe" }); + execSync("git config user.email 'test@example.com'", { cwd: repoDir, stdio: "pipe" }); + execSync("git config user.name 'Test User'", { cwd: repoDir, stdio: "pipe" }); + + fs.writeFileSync(path.join(repoDir, "README.md"), "base\n"); + execSync("git add README.md", { cwd: repoDir, stdio: "pipe" }); + execSync("git commit -m 'base'", { cwd: repoDir, stdio: "pipe" }); + + execSync("git checkout -b feature/work", { cwd: repoDir, stdio: "pipe" }); + // Add a disallowed file — but since origin/main is absent the check must be skipped + fs.writeFileSync(path.join(repoDir, "script.js"), "// disallowed\n"); + execSync("git add script.js", { cwd: repoDir, stdio: "pipe" }); + execSync("git commit -m 'disallowed file'", { cwd: repoDir, stdio: "pipe" }); + // No remote / no origin/main tracking ref + } catch { + return; + } + + process.env.GITHUB_BASE_REF = "main"; + process.env.GITHUB_WORKSPACE = repoDir; + const localHandlers = createHandlers(mockServer, mockAppendSafeOutput, { + push_to_pull_request_branch: { + allowed_files: ["*.md"], + }, + }); + + try { + const result = await localHandlers.pushToPullRequestBranchHandler({ branch: "feature/work" }); + + // The non-fatal catch must not have blocked execution with an allowed-files error + if (result.isError) { + const data = JSON.parse(result.content[0].text); + expect(data.error).not.toContain("allowed-files configuration"); + expect(data.disallowed_files).toBeUndefined(); + } + } finally { + delete process.env.GITHUB_BASE_REF; + process.env.GITHUB_WORKSPACE = testWorkspaceDir; + } + }); + }); }); describe("handler structure", () => { From 08c40b878b7e055176116b15cd88ec80a5e71ebb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Jul 2026 03:27:53 +0000 Subject: [PATCH 5/8] fix: address code review issues - TOCTOU, empty baseBranch, coreLogger threading Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../setup/js/push_to_pull_request_branch.cjs | 11 +-- actions/setup/js/safe_outputs_handlers.cjs | 76 ++++++++++--------- 2 files changed, 48 insertions(+), 39 deletions(-) diff --git a/actions/setup/js/push_to_pull_request_branch.cjs b/actions/setup/js/push_to_pull_request_branch.cjs index ffd43add66b..31cb90d2e83 100644 --- a/actions/setup/js/push_to_pull_request_branch.cjs +++ b/actions/setup/js/push_to_pull_request_branch.cjs @@ -156,17 +156,18 @@ function isWorkflowsScopeRejection(stderr) { * @param {{ getExecOutput: Function }} exec - @actions/exec module (or compatible mock) * @param {Record} gitOptions - Base git exec options (cwd, env, etc.) * @param {string | undefined} baseBranch - PR base branch name (e.g. "main"); falls back to origin/HEAD when not provided + * @param {typeof core} coreLogger - Actions core logger used for debug output * @returns {Promise} Unique workflow file paths found in the branch history */ -async function detectWorkflowFileChanges(exec, gitOptions, baseBranch) { - const baseline = baseBranch ? `origin/${baseBranch}` : "origin/HEAD"; +async function detectWorkflowFileChanges(exec, gitOptions, baseBranch, coreLogger) { + const baseline = baseBranch && baseBranch.trim() ? `origin/${baseBranch}` : "origin/HEAD"; try { const result = await exec.getExecOutput("git", ["log", "--name-only", "--pretty=format:", "HEAD", "--not", baseline, "--", ".github/workflows/"], { ...gitOptions, ignoreReturnCode: true }); if (result.exitCode !== 0) { // Non-zero exit means the baseline ref was not resolvable or git failed; // treat as no workflow changes so the push proceeds and any real scope // rejection surfaces downstream. - core.debug(`detectWorkflowFileChanges: git log exited ${result.exitCode} (baseline '${baseline}' may be unavailable); skipping pre-flight`); + coreLogger.debug(`detectWorkflowFileChanges: git log exited ${result.exitCode} (baseline '${baseline}' may be unavailable); skipping pre-flight`); return []; } return [ @@ -178,7 +179,7 @@ async function detectWorkflowFileChanges(exec, gitOptions, baseBranch) { ), ]; } catch (err) { - core.debug(`detectWorkflowFileChanges: git log threw (baseline '${baseline}'); skipping pre-flight: ${err instanceof Error ? err.message : String(err)}`); + coreLogger.debug(`detectWorkflowFileChanges: git log threw (baseline '${baseline}'); skipping pre-flight: ${err instanceof Error ? err.message : String(err)}`); return []; } } @@ -201,7 +202,7 @@ async function detectWorkflowFileChanges(exec, gitOptions, baseBranch) { */ async function runWorkflowScopePreflightCheck(exec, gitOptions, allowWorkflows, baseBranch, context, coreLogger) { if (allowWorkflows) return null; - const workflowFiles = await detectWorkflowFileChanges(exec, gitOptions, baseBranch); + const workflowFiles = await detectWorkflowFileChanges(exec, gitOptions, baseBranch, coreLogger); if (workflowFiles.length > 0) { coreLogger.info(`Pre-flight check: branch history contains workflow file changes (${workflowFiles.join(", ")}). Failing before push attempt.`); return buildWorkflowsScopeError(`${context} pre-flight`, coreLogger); diff --git a/actions/setup/js/safe_outputs_handlers.cjs b/actions/setup/js/safe_outputs_handlers.cjs index 91ca07c790a..c04a086ce77 100644 --- a/actions/setup/js/safe_outputs_handlers.cjs +++ b/actions/setup/js/safe_outputs_handlers.cjs @@ -1259,41 +1259,49 @@ function createHandlers(server, appendSafeOutput, config = {}) { // before any transport artifacts are generated. if (Array.isArray(pushConfig.allowed_files) && pushConfig.allowed_files.length > 0) { try { - const branchHistoryFiles = execGitSync(["log", "--name-only", "--pretty=format:", `origin/${baseBranch}..${pushPinnedSha ?? entry.branch}`, "--"], { cwd: pushGitCwd }) - .toString() - .split("\n") - .map(s => s.trim()) - .filter(Boolean); - - if (branchHistoryFiles.length > 0) { - const allowedPatterns = pushConfig.allowed_files.map(p => globPatternToRegex(p)); - // Files matching excluded_files are intentionally exempt: they will be stripped - // from the patch at generation time via :(exclude) pathspecs, so they won't be - // present in the final changeset applied to the branch. - const excludedPatterns = Array.isArray(pushConfig.excluded_files) ? pushConfig.excluded_files.map(p => globPatternToRegex(p)) : []; - const uniqueFiles = [...new Set(branchHistoryFiles)]; - const disallowedFiles = uniqueFiles.filter(f => !allowedPatterns.some(re => re.test(f)) && !excludedPatterns.some(re => re.test(f))); - - if (disallowedFiles.length > 0) { - const sample = disallowedFiles.slice(0, 5); - const remaining = disallowedFiles.length - sample.length; - const filesStr = remaining > 0 ? `${sample.join(", ")} (+${remaining} more)` : sample.join(", "); - server.debug(`Full-branch allowed-files check failed: ${filesStr}`); - return { - content: [ - { - type: "text", - text: JSON.stringify({ - result: "error", - error: `Cannot push to pull request branch: the branch '${entry.branch}' history contains commits that modify files outside the allowed-files configuration: ${filesStr}. Remove the disallowed file changes from your commits and retry, or update the allowed-files configuration to include these files.`, - disallowed_files: disallowedFiles, - }), - }, - ], - isError: true, - }; + // Use the pinned SHA as the range head to avoid any TOCTOU window between + // the time the SHA was recorded and the time of the git log query. If no + // pinned SHA is available (e.g. non-bundle path), skip the check so we do + // not race against a mutable ref; the apply-time check still enforces policy. + if (!pushPinnedSha) { + server.debug("Full-branch allowed-files check skipped: branch SHA not pinned (non-bundle path)"); + } else { + const branchHistoryFiles = execGitSync(["log", "--name-only", "--pretty=format:", `origin/${baseBranch}..${pushPinnedSha}`, "--"], { cwd: pushGitCwd }) + .toString() + .split("\n") + .map(s => s.trim()) + .filter(Boolean); + + if (branchHistoryFiles.length > 0) { + const allowedPatterns = pushConfig.allowed_files.map(p => globPatternToRegex(p)); + // Files matching excluded_files are intentionally exempt: they will be stripped + // from the patch at generation time via :(exclude) pathspecs, so they won't be + // present in the final changeset applied to the branch. + const excludedPatterns = Array.isArray(pushConfig.excluded_files) ? pushConfig.excluded_files.map(p => globPatternToRegex(p)) : []; + const uniqueFiles = [...new Set(branchHistoryFiles)]; + const disallowedFiles = uniqueFiles.filter(f => !allowedPatterns.some(re => re.test(f)) && !excludedPatterns.some(re => re.test(f))); + + if (disallowedFiles.length > 0) { + const sample = disallowedFiles.slice(0, 5); + const remaining = disallowedFiles.length - sample.length; + const filesStr = remaining > 0 ? `${sample.join(", ")} (+${remaining} more)` : sample.join(", "); + server.debug(`Full-branch allowed-files check failed: ${filesStr}`); + return { + content: [ + { + type: "text", + text: JSON.stringify({ + result: "error", + error: `Cannot push to pull request branch: the branch '${entry.branch}' history contains commits that modify files outside the allowed-files configuration: ${filesStr}. Remove the disallowed file changes from your commits and retry, or update the allowed-files configuration to include these files.`, + disallowed_files: disallowedFiles, + }), + }, + ], + isError: true, + }; + } } - } + } // end else (pushPinnedSha available) } catch (fullBranchCheckError) { // Non-fatal: if origin/baseBranch is not available locally or git fails, // skip the full-branch check and continue. The apply-time policy check in From abb0ace6c2781333ceafb02a8ab891c84fa5f581 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Jul 2026 05:01:33 +0000 Subject: [PATCH 6/8] fix: initialize copilotConnectionToken to "" to resolve TS2322 typecheck failure; remove unnecessary block scoping and end-else comment Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/daily-team-status.lock.yml | 16 ++--- actions/setup/js/copilot_harness.cjs | 65 +++++++++++++++---- .../setup/js/push_to_pull_request_branch.cjs | 12 ++-- actions/setup/js/safe_outputs_handlers.cjs | 2 +- 4 files changed, 66 insertions(+), 29 deletions(-) diff --git a/.github/workflows/daily-team-status.lock.yml b/.github/workflows/daily-team-status.lock.yml index 12b2c936b1f..e48da18cca8 100644 --- a/.github/workflows/daily-team-status.lock.yml +++ b/.github/workflows/daily-team-status.lock.yml @@ -1,5 +1,5 @@ # gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"9f61ffba0d4e9663a43f41030c13a4c7c71ebac8122f586cbd706756a605df94","body_hash":"33c10cc22b8836b79387efda582e48c5a463e9849880a01d58704a0fa291e986","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","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":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.16","digest":"sha256:94bbf52b74d38e8117387e93e698f79d678dd3879faa0e57f2ea128eda8fb507","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.16@sha256:94bbf52b74d38e8117387e93e698f79d678dd3879faa0e57f2ea128eda8fb507"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16","digest":"sha256:bbad2f109b97a4b3375ad371a5300d42bc9251dad61cd7bc66380cad8501cf94","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16@sha256:bbad2f109b97a4b3375ad371a5300d42bc9251dad61cd7bc66380cad8501cf94"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.16","digest":"sha256:483d6a8086752a02d581d7a42629b741e3f2fa9f3a6a10320590cf881638dad3","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.16@sha256:483d6a8086752a02d581d7a42629b741e3f2fa9f3a6a10320590cf881638dad3"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.32","digest":"sha256:63e46b56dfd70895a701b6fc6dd0189e11e2d875f327f1781e81b31848735477","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.32@sha256:63e46b56dfd70895a701b6fc6dd0189e11e2d875f327f1781e81b31848735477"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.5.0","digest":"sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4","pinned_image":"ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4"}]} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","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":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.16"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.16"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.32","digest":"sha256:63e46b56dfd70895a701b6fc6dd0189e11e2d875f327f1781e81b31848735477","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.32@sha256:63e46b56dfd70895a701b6fc6dd0189e11e2d875f327f1781e81b31848735477"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.5.0","digest":"sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4","pinned_image":"ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4"}]} # 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 # # ___ _ _ @@ -47,9 +47,9 @@ # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.27.16@sha256:94bbf52b74d38e8117387e93e698f79d678dd3879faa0e57f2ea128eda8fb507 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16@sha256:bbad2f109b97a4b3375ad371a5300d42bc9251dad61cd7bc66380cad8501cf94 -# - ghcr.io/github/gh-aw-firewall/squid:0.27.16@sha256:483d6a8086752a02d581d7a42629b741e3f2fa9f3a6a10320590cf881638dad3 +# - ghcr.io/github/gh-aw-firewall/agent:0.27.16 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16 +# - ghcr.io/github/gh-aw-firewall/squid:0.27.16 # - ghcr.io/github/gh-aw-mcpg:v0.3.32@sha256:63e46b56dfd70895a701b6fc6dd0189e11e2d875f327f1781e81b31848735477 # - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b # - ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 @@ -507,7 +507,7 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.16@sha256:94bbf52b74d38e8117387e93e698f79d678dd3879faa0e57f2ea128eda8fb507 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16@sha256:bbad2f109b97a4b3375ad371a5300d42bc9251dad61cd7bc66380cad8501cf94 ghcr.io/github/gh-aw-firewall/squid:0.27.16@sha256:483d6a8086752a02d581d7a42629b741e3f2fa9f3a6a10320590cf881638dad3 ghcr.io/github/gh-aw-mcpg:v0.3.32@sha256:63e46b56dfd70895a701b6fc6dd0189e11e2d875f327f1781e81b31848735477 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.16 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16 ghcr.io/github/gh-aw-firewall/squid:0.27.16 ghcr.io/github/gh-aw-mcpg:v0.3.32@sha256:63e46b56dfd70895a701b6fc6dd0189e11e2d875f327f1781e81b31848735477 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" @@ -788,7 +788,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.16/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"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\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.5\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.16,squid=sha256:483d6a8086752a02d581d7a42629b741e3f2fa9f3a6a10320590cf881638dad3,agent=sha256:94bbf52b74d38e8117387e93e698f79d678dd3879faa0e57f2ea128eda8fb507,api-proxy=sha256:bbad2f109b97a4b3375ad371a5300d42bc9251dad61cd7bc66380cad8501cf94,cli-proxy=sha256:8d83ae45bc1b7a705dc99ef4f76ec782792bbf3561c6923f350b34a68a577bae\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.16/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"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\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.5\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.16\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" @@ -1316,7 +1316,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.16@sha256:94bbf52b74d38e8117387e93e698f79d678dd3879faa0e57f2ea128eda8fb507 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16@sha256:bbad2f109b97a4b3375ad371a5300d42bc9251dad61cd7bc66380cad8501cf94 ghcr.io/github/gh-aw-firewall/squid:0.27.16@sha256:483d6a8086752a02d581d7a42629b741e3f2fa9f3a6a10320590cf881638dad3 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.16 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.16 ghcr.io/github/gh-aw-firewall/squid:0.27.16 - name: Check if detection needed id: detection_guard if: always() @@ -1403,7 +1403,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.16/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5},\"container\":{\"imageTag\":\"0.27.16,squid=sha256:483d6a8086752a02d581d7a42629b741e3f2fa9f3a6a10320590cf881638dad3,agent=sha256:94bbf52b74d38e8117387e93e698f79d678dd3879faa0e57f2ea128eda8fb507,api-proxy=sha256:bbad2f109b97a4b3375ad371a5300d42bc9251dad61cd7bc66380cad8501cf94,cli-proxy=sha256:8d83ae45bc1b7a705dc99ef4f76ec782792bbf3561c6923f350b34a68a577bae\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.16/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5},\"container\":{\"imageTag\":\"0.27.16\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" diff --git a/actions/setup/js/copilot_harness.cjs b/actions/setup/js/copilot_harness.cjs index 6c91d77fbc5..144cc16e005 100644 --- a/actions/setup/js/copilot_harness.cjs +++ b/actions/setup/js/copilot_harness.cjs @@ -87,7 +87,11 @@ const COPILOT_REQUESTS_PROXY_AUTH_403_TEMPLATE_NAME = "copilot_requests_proxy_au const CAPI_ERROR_400_PATTERN = /CAPIError:\s*400/; // Pattern to detect generic HTTP 400 Bad Request responses emitted by engine CLI / SDK wrappers. // NOTE: keep in sync with HTTP_400_RESPONSE_ERROR_PATTERN in detect_agent_errors.cjs. -const HTTP_400_RESPONSE_ERROR_PATTERN = /Response status code does not indicate success:\s*400(?:\s*\(Bad Request\))?/i; +// Also matches "400 400 400 no model endpoints available given user constraints" which is emitted +// by the Copilot SDK when no model endpoints are available for the user's configured constraints. +// The second alternative is anchored to a leading "400" to avoid false positives from unrelated +// diagnostic or informational messages that might contain the phrase. +const HTTP_400_RESPONSE_ERROR_PATTERN = /(?:Response status code does not indicate success:\s*400(?:\s*\(Bad Request\))?|400[^\n]*no model endpoints available given user constraints)/i; // Pattern to detect MCP servers blocked by enterprise/organization policy. // This is a persistent policy configuration error — retrying will not help. @@ -493,6 +497,37 @@ function detectCopilotErrors(output) { }; } +/** + * Build child-process environment additions for Copilot SDK mode. + * @param {{ + * sdkEnv: NodeJS.ProcessEnv, + * copilotSDKMode: boolean, + * copilotConnectionToken: string, + * providerBaseUrl: string, + * providerType: string, + * providerWireApi: string, + * resolvedModel: string, + * }} options + * @returns {NodeJS.ProcessEnv} + */ +function buildCopilotSDKChildEnv({ sdkEnv, copilotSDKMode, copilotConnectionToken, providerBaseUrl, providerType, providerWireApi, resolvedModel }) { + if (!copilotSDKMode) { + return sdkEnv; + } + return { + ...sdkEnv, + COPILOT_CONNECTION_TOKEN: copilotConnectionToken, + GH_AW_COPILOT_SDK_PROVIDER_BASE_URL: providerBaseUrl, + GH_AW_COPILOT_SDK_PROVIDER_TYPE: providerType, + ...(providerWireApi ? { GH_AW_COPILOT_SDK_PROVIDER_WIRE_API: providerWireApi } : {}), + COPILOT_MODEL: resolvedModel, + // Native Copilot CLI BYOK env vars — consumed by the headless sidecar for all sessions. + COPILOT_PROVIDER_BASE_URL: providerBaseUrl, + COPILOT_PROVIDER_TYPE: providerType, + ...(providerWireApi ? { COPILOT_PROVIDER_WIRE_API: providerWireApi } : {}), + }; +} + /** * Write Copilot detection outputs to $GITHUB_OUTPUT. * @param {{ inferenceAccessError: boolean, mcpPolicyError: boolean, agenticEngineTimeout: boolean, modelNotSupportedError: boolean, http400ResponseError: boolean }} results @@ -680,7 +715,7 @@ async function main() { // correct SDK endpoint URI. const sdkEnv = buildCopilotSDKEnv(); const copilotSDKMode = isCopilotSDKEnabled(); - let copilotConnectionToken; + let copilotConnectionToken = ""; if (copilotSDKMode) { // The harness always generates the connection token when SDK mode is active. // The token is injected into the driver subprocess env so the harness-managed @@ -742,16 +777,21 @@ async function main() { // (started by the harness) and the SDK client share the same token. // In SDK mode also inject the resolved BYOK provider base URL, type, and model so the driver // subprocess does not need to re-read the reflect file. - const sdkChildEnv = copilotSDKMode - ? { - ...sdkEnv, - COPILOT_CONNECTION_TOKEN: copilotConnectionToken, - GH_AW_COPILOT_SDK_PROVIDER_BASE_URL: providerBaseUrl, - GH_AW_COPILOT_SDK_PROVIDER_TYPE: providerType, - GH_AW_COPILOT_SDK_PROVIDER_WIRE_API: providerWireApi, - COPILOT_MODEL: resolvedModel, - } - : sdkEnv; + // + // Additionally, forward BYOK config as native Copilot CLI COPILOT_PROVIDER_* env vars so + // the headless sidecar propagates the same provider to sub-agent sessions spawned via the + // task tool. Sub-agents do not inherit the SDK session-level `provider` config; the headless + // server instead reads COPILOT_PROVIDER_* from its own process env to configure each + // sub-agent session's inference backend. + const sdkChildEnv = buildCopilotSDKChildEnv({ + sdkEnv, + copilotSDKMode, + copilotConnectionToken, + providerBaseUrl, + providerType, + providerWireApi, + resolvedModel, + }); const childEnv = Object.keys(sdkChildEnv).length > 0 ? { ...process.env, ...sdkChildEnv } : undefined; // Pre-flight: skip the agent entirely when a noop has already been written by a prior step. @@ -1105,6 +1145,7 @@ if (typeof module !== "undefined" && module.exports) { fetchAWFReflect, fetchModelsFromUrl, buildCopilotProxyAuthFailureDiagnostic, + buildCopilotSDKChildEnv, envFlagEnabled, generateCopilotConnectionToken, buildCopilotSDKServerArgs, diff --git a/actions/setup/js/push_to_pull_request_branch.cjs b/actions/setup/js/push_to_pull_request_branch.cjs index 31cb90d2e83..38da3fc5ed5 100644 --- a/actions/setup/js/push_to_pull_request_branch.cjs +++ b/actions/setup/js/push_to_pull_request_branch.cjs @@ -1125,10 +1125,8 @@ async function main(config = {}) { // even if the current changeset itself does not touch workflow files. // Failing here avoids leaving the local branch in a renamed state after // a rejected push, and surfaces the error before any side effects. - { - const preflightError = await runWorkflowScopePreflightCheck(exec, baseGitOpts, allowWorkflows, pullRequest?.base?.ref, "Review branch", core); - if (preflightError) return preflightError; - } + const preflightError = await runWorkflowScopePreflightCheck(exec, baseGitOpts, allowWorkflows, pullRequest?.base?.ref, "Review branch", core); + if (preflightError) return preflightError; // Rename current local branch to review branch await exec.exec("git", ["checkout", "-b", reviewBranchName], baseGitOpts); @@ -1302,10 +1300,8 @@ async function main(config = {}) { // Pre-flight: check full branch history for workflow file changes. // Like the review branch path, creating a new fallback branch ref triggers // GitHub's scope check on the full commit history, not just the new commits. - { - const preflightError = await runWorkflowScopePreflightCheck(exec, baseGitOpts, allowWorkflows, pullRequest?.base?.ref, "Fallback branch", core); - if (preflightError) return preflightError; - } + const preflightError = await runWorkflowScopePreflightCheck(exec, baseGitOpts, allowWorkflows, pullRequest?.base?.ref, "Fallback branch", core); + if (preflightError) return preflightError; await exec.exec("git", ["checkout", "-b", fallbackBranchName], baseGitOpts); // Use getExecOutput to capture stderr for 'workflows' scope diagnostics diff --git a/actions/setup/js/safe_outputs_handlers.cjs b/actions/setup/js/safe_outputs_handlers.cjs index c04a086ce77..fe864bfba5a 100644 --- a/actions/setup/js/safe_outputs_handlers.cjs +++ b/actions/setup/js/safe_outputs_handlers.cjs @@ -1301,7 +1301,7 @@ function createHandlers(server, appendSafeOutput, config = {}) { }; } } - } // end else (pushPinnedSha available) + } } catch (fullBranchCheckError) { // Non-fatal: if origin/baseBranch is not available locally or git fails, // skip the full-branch check and continue. The apply-time policy check in From bdc77eb044c3c449ce82a69496057f8e8139d3cd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Jul 2026 05:26:32 +0000 Subject: [PATCH 7/8] chore: initial plan for fixing lint-errors CI failure Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/importinpututil/import_input.go | 9 +++++++++ pkg/intent/resolver.go | 13 +++++++++++++ 2 files changed, 22 insertions(+) diff --git a/pkg/importinpututil/import_input.go b/pkg/importinpututil/import_input.go index 7f4f0a16dda..ceac05694ff 100644 --- a/pkg/importinpututil/import_input.go +++ b/pkg/importinpututil/import_input.go @@ -6,25 +6,33 @@ import ( "reflect" "sort" "strings" + + "github.com/github/gh-aw/pkg/logger" ) +var importInputLog = logger.New("importinpututil:import_input") + // ResolvePathValue resolves either a top-level input key ("count") or a one-level // dotted object sub-key ("config.apiKey") from import inputs. func ResolvePathValue(inputs map[string]any, inputPath string) (any, bool) { top, sub, hasDot := strings.Cut(inputPath, ".") if !hasDot { value, ok := inputs[top] + importInputLog.Printf("ResolvePathValue: top-level key %q found=%t", top, ok) return value, ok } topVal, topOK := inputs[top] if !topOK { + importInputLog.Printf("ResolvePathValue: parent key %q not found for path %q", top, inputPath) return nil, false } obj, isMap := topVal.(map[string]any) if !isMap { + importInputLog.Printf("ResolvePathValue: parent key %q is not an object for path %q", top, inputPath) return nil, false } value, ok := obj[sub] + importInputLog.Printf("ResolvePathValue: sub-key %q under %q found=%t", sub, top, ok) return value, ok } @@ -38,6 +46,7 @@ func FormatResolvedValue(value any) (string, bool) { case map[string]any: return marshalValue(v) case nil: + importInputLog.Print("FormatResolvedValue: nil value, no substitution") return "", false default: return formatReflectiveValue(v) diff --git a/pkg/intent/resolver.go b/pkg/intent/resolver.go index 53005a5bccc..dca9df429fc 100644 --- a/pkg/intent/resolver.go +++ b/pkg/intent/resolver.go @@ -1,5 +1,9 @@ package intent +import "github.com/github/gh-aw/pkg/logger" + +var resolverLog = logger.New("intent:resolver") + type AttributionStatus string const ( @@ -60,29 +64,38 @@ type Resolver struct { } func (r Resolver) ResolvePullRequest(pr PullRequestData) IntentRecord { + resolverLog.Printf("ResolvePullRequest: nodeID=%s explicitIntent=%t closingIssues=%d labels=%d", pr.NodeID, pr.ExplicitIntent != nil, len(pr.ClosingIssues), len(pr.Labels)) + if pr.ExplicitIntent != nil { intent := *pr.ExplicitIntent if intent.ResolverVersion == "" { intent.ResolverVersion = r.ResolverVersion } + resolverLog.Printf("ResolvePullRequest: using explicit metadata for %s", pr.NodeID) return intent } switch len(pr.ClosingIssues) { case 1: + resolverLog.Printf("ResolvePullRequest: attributing %s to single closing issue", pr.NodeID) return r.fromRoot(pr.ClosingIssues[0], SourceClosingIssue, "single_closing_issue") case 0: if len(pr.Labels) > 0 { + resolverLog.Printf("ResolvePullRequest: no closing issue, falling back to %d PR label(s) for %s", len(pr.Labels), pr.NodeID) return r.fromLabels(pr.NodeID, pr.URL, pr.Labels, SourceArtifactLabels, "pull_request_label_fallback") } + resolverLog.Printf("ResolvePullRequest: no intent source for %s, marking unlinked", pr.NodeID) return r.unlinked("no_supported_intent_source") default: + resolverLog.Printf("ResolvePullRequest: %d closing issues for %s, marking ambiguous", len(pr.ClosingIssues), pr.NodeID) return r.ambiguous(SourceClosingIssue, "multiple_closing_issues") } } func (r Resolver) ResolveIssue(nodeID, url string, labels []string) IntentRecord { + resolverLog.Printf("ResolveIssue: nodeID=%s labels=%d", nodeID, len(labels)) if len(labels) == 0 { + resolverLog.Printf("ResolveIssue: no labels for %s, marking unlinked", nodeID) return r.unlinked("no_supported_intent_source") } return IntentRecord{ From 1f76475594223cbd2eb2ac3285a87e6d08506b34 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Jul 2026 05:33:39 +0000 Subject: [PATCH 8/8] fix: merge origin/main and add Example: sections to skills_frontmatter.go error messages to fix lint-errors CI Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/skills_frontmatter.go | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/pkg/workflow/skills_frontmatter.go b/pkg/workflow/skills_frontmatter.go index 7a6b5e86e38..237c4fc57b1 100644 --- a/pkg/workflow/skills_frontmatter.go +++ b/pkg/workflow/skills_frontmatter.go @@ -26,16 +26,17 @@ type SkillReference struct { func validateSkillSpecValue(skillSpec string, idx int) error { if strings.TrimSpace(skillSpec) == "" { - return fmt.Errorf("skills[%d] must be a non-empty string", idx) + return fmt.Errorf("skills[%d] must be a non-empty string. Example: skills[%d]: \"owner/repo@abc1234...\"", idx, idx) } if githubActionsExpressionRegexp.MatchString(skillSpec) || skillSpecExpressionRefRegexp.MatchString(skillSpec) { return nil } if !skillSpecRegexp.MatchString(skillSpec) { return fmt.Errorf( - "skills[%d] must use owner/repo@<40-char-sha>, owner/repo/skill/path@<40-char-sha>, or a GitHub Actions expression: %q", + "skills[%d] must use owner/repo@<40-char-sha>, owner/repo/skill/path@<40-char-sha>, or a GitHub Actions expression (got %q). Example: skills[%d]: \"owner/repo@abcdef1234567890abcdef1234567890abcdef12\"", idx, skillSpec, + idx, ) } return nil @@ -49,7 +50,7 @@ func validateFrontmatterSkills(frontmatter map[string]any) error { skills, ok := rawSkills.([]any) if !ok { - return errors.New("skills must be an array of skill references") + return errors.New("skills must be an array of skill references. Example: skills: [\"owner/repo@sha\"]") } skillsFrontmatterLog.Printf("validateFrontmatterSkills: validating %d skill entr(ies)", len(skills)) @@ -62,18 +63,18 @@ func validateFrontmatterSkills(frontmatter map[string]any) error { } case map[string]any: if len(typed) == 0 { - return fmt.Errorf("skills[%d] must include a non-empty skill field", i) + return fmt.Errorf("skills[%d] must include a non-empty skill field. Example: skills[%d]: {skill: \"owner/repo@sha\"}", i, i) } skillValue, hasSkill := typed["skill"] if !hasSkill { - return fmt.Errorf("skills[%d].skill is required", i) + return fmt.Errorf("skills[%d].skill is required. Example: skills[%d].skill: \"owner/repo@sha\"", i, i) } skillSpec, ok := skillValue.(string) if !ok { - return fmt.Errorf("skills[%d].skill must be a string", i) + return fmt.Errorf("skills[%d].skill must be a string. Example: skills[%d].skill: \"owner/repo@sha\"", i, i) } if strings.TrimSpace(skillSpec) == "" { - return fmt.Errorf("skills[%d].skill must be a non-empty string", i) + return fmt.Errorf("skills[%d].skill must be a non-empty string. Example: skills[%d].skill: \"owner/repo@sha\"", i, i) } if err := validateSkillSpecValue(skillSpec, i); err != nil { return err @@ -94,11 +95,12 @@ func validateFrontmatterSkills(frontmatter map[string]any) error { if tokenValue, hasToken := typed["github-token"]; hasToken { token, ok := tokenValue.(string) if !ok { - return fmt.Errorf("skills[%d].github-token must be a string", i) + return fmt.Errorf("skills[%d].github-token must be a string. Example: skills[%d].github-token: \"${{ secrets.MY_TOKEN }}\"", i, i) } if !githubTokenExpressionRegexp.MatchString(token) { return fmt.Errorf( - "skills[%d].github-token must be a valid GitHub token expression (e.g., '${{ secrets.NAME }}' or '${{ needs.auth.outputs.token }}')", + "skills[%d].github-token must be a valid GitHub token expression. Example: skills[%d].github-token: \"${{ secrets.NAME }}\" or \"${{ needs.auth.outputs.token }}\"", + i, i, ) } @@ -106,15 +108,15 @@ func validateFrontmatterSkills(frontmatter map[string]any) error { if app, hasApp := typed["github-app"]; hasApp { appMap, ok := app.(map[string]any) if !ok { - return fmt.Errorf("skills[%d].github-app must be an object", i) + return fmt.Errorf("skills[%d].github-app must be an object. Example: skills[%d].github-app: {client-id: \"Iv1.abc\", private-key: \"...\"}", i, i) } parsed := parseAppConfig(appMap) if !parsed.hasRequiredCredentials() { - return fmt.Errorf("skills[%d].github-app must include non-empty client-id/app-id and private-key", i) + return fmt.Errorf("skills[%d].github-app must include non-empty client-id/app-id and private-key. Example: skills[%d].github-app: {client-id: \"Iv1.abc\", private-key: \"...\"}", i, i) } } default: - return fmt.Errorf("skills[%d] must be a string or object", i) + return fmt.Errorf("skills[%d] must be a string or object. Example: skills[%d]: \"owner/repo@sha\" or {skill: \"owner/repo@sha\"}", i, i) } }