diff --git a/.github/workflows/dev.lock.yml b/.github/workflows/dev.lock.yml
index 7baee9df76e..efccfbbaa57 100644
--- a/.github/workflows/dev.lock.yml
+++ b/.github/workflows/dev.lock.yml
@@ -482,8 +482,6 @@ jobs:
run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.51
- name: Install Pi CLI
run: npm install --ignore-scripts -g @earendil-works/pi-coding-agent@0.75.4
- env:
- NPM_CONFIG_MIN_RELEASE_AGE: '3'
- name: Determine automatic lockdown mode for GitHub MCP Server
id: determine-automatic-lockdown
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9)
@@ -1242,8 +1240,6 @@ jobs:
run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.51
- name: Install Pi CLI
run: npm install --ignore-scripts -g @earendil-works/pi-coding-agent@0.75.4
- env:
- NPM_CONFIG_MIN_RELEASE_AGE: '3'
- name: Execute Pi CLI
if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
diff --git a/.github/workflows/smoke-pi.lock.yml b/.github/workflows/smoke-pi.lock.yml
index 15e624313de..1904b47164a 100644
--- a/.github/workflows/smoke-pi.lock.yml
+++ b/.github/workflows/smoke-pi.lock.yml
@@ -1,4 +1,4 @@
-# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"e415b31c26975f554469a7358fcb84d9481f30b8a188b6b20971e6fd7ba7c4c2","strict":true,"agent_id":"pi","agent_model":"copilot/claude-sonnet-4-20250514"}
+# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"f4c7fd731f56fc22355e72c83ebe8e15e64a025dbe359da4034d627f19974da4","strict":true,"agent_id":"pi","agent_model":"copilot/claude-sonnet-4-20250514"}
# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"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.25.51"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.51"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.51"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.51"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.17"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]}
# ___ _ _
# / _ \ | | (_)
@@ -222,7 +222,7 @@ jobs:
id: sanitized
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.grafana.net,*.sentry.io,127.0.0.1,::1,api.githubcopilot.com,api.pi.ai,api.snapcraft.io,app.renovatebot.com,appveyor.com,archive.ubuntu.com,azure.archive.ubuntu.com,badgen.net,circleci.com,codacy.com,codeclimate.com,codecov.io,codeload.github.com,coveralls.io,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,deepsource.io,docs.github.com,drone.io,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,img.shields.io,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,localhost,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,ppa.launchpad.net,raw.githubusercontent.com,readthedocs.io,readthedocs.org,registry.npmjs.org,renovatebot.com,s.symcb.com,s.symcd.com,security.ubuntu.com,semaphoreci.com,shields.io,snyk.io,sonarcloud.io,sonarqube.com,travis-ci.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.grafana.net,*.sentry.io,127.0.0.1,::1,api.githubcopilot.com,api.npms.io,api.pi.ai,api.snapcraft.io,app.renovatebot.com,appveyor.com,archive.ubuntu.com,azure.archive.ubuntu.com,badgen.net,bun.sh,cdn.jsdelivr.net,circleci.com,codacy.com,codeclimate.com,codecov.io,codeload.github.com,coveralls.io,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,deb.nodesource.com,deepsource.io,deno.land,docs.github.com,drone.io,esm.sh,get.pnpm.io,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,googleapis.deno.dev,googlechromelabs.github.io,host.docker.internal,img.shields.io,json-schema.org,json.schemastore.org,jsr.io,keyserver.ubuntu.com,lfs.github.com,localhost,nodejs.org,npm.pkg.github.com,npmjs.com,npmjs.org,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,ppa.launchpad.net,raw.githubusercontent.com,readthedocs.io,readthedocs.org,registry.bower.io,registry.npmjs.com,registry.npmjs.org,registry.yarnpkg.com,renovatebot.com,repo.yarnpkg.com,s.symcb.com,s.symcd.com,security.ubuntu.com,semaphoreci.com,shields.io,skimdb.npmjs.com,snyk.io,sonarcloud.io,sonarqube.com,storage.googleapis.com,telemetry.vercel.com,travis-ci.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.npmjs.com,www.npmjs.org,yarnpkg.com"
with:
script: |
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
@@ -259,21 +259,21 @@ jobs:
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
{
- cat << 'GH_AW_PROMPT_c2411102b3e16c3c_EOF'
+ cat << 'GH_AW_PROMPT_699af9e2789b8ab9_EOF'
- GH_AW_PROMPT_c2411102b3e16c3c_EOF
+ GH_AW_PROMPT_699af9e2789b8ab9_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/cache_memory_prompt.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md"
- cat << 'GH_AW_PROMPT_c2411102b3e16c3c_EOF'
+ cat << 'GH_AW_PROMPT_699af9e2789b8ab9_EOF'
Tools: add_comment(max:2), create_issue, add_labels, missing_tool, missing_data, noop
- GH_AW_PROMPT_c2411102b3e16c3c_EOF
+ GH_AW_PROMPT_699af9e2789b8ab9_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
- cat << 'GH_AW_PROMPT_c2411102b3e16c3c_EOF'
+ cat << 'GH_AW_PROMPT_699af9e2789b8ab9_EOF'
The following GitHub context information is available for this workflow:
{{#if github.actor}}
@@ -302,9 +302,9 @@ jobs:
{{/if}}
- GH_AW_PROMPT_c2411102b3e16c3c_EOF
+ GH_AW_PROMPT_699af9e2789b8ab9_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/cli_proxy_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_c2411102b3e16c3c_EOF'
+ cat << 'GH_AW_PROMPT_699af9e2789b8ab9_EOF'
{{#runtime-import .github/workflows/shared/gh.md}}
{{#runtime-import .github/workflows/shared/reporting-otlp.md}}
@@ -312,7 +312,7 @@ jobs:
{{#runtime-import .github/workflows/shared/reporting.md}}
{{#runtime-import .github/workflows/shared/noop-reminder.md}}
{{#runtime-import .github/workflows/smoke-pi.md}}
- GH_AW_PROMPT_c2411102b3e16c3c_EOF
+ GH_AW_PROMPT_699af9e2789b8ab9_EOF
} > "$GH_AW_PROMPT"
- name: Interpolate variables and render templates
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -460,6 +460,11 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
+ - name: Setup Node.js
+ uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
+ with:
+ node-version: '24'
+ package-manager-cache: false
- name: Create gh-aw temp directory
run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh"
- name: Configure gh CLI for GitHub Enterprise
@@ -517,8 +522,6 @@ jobs:
run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.51
- name: Install Pi CLI
run: npm install --ignore-scripts -g @earendil-works/pi-coding-agent@0.75.4
- env:
- NPM_CONFIG_MIN_RELEASE_AGE: '3'
- name: Determine automatic lockdown mode for GitHub MCP Server
id: determine-automatic-lockdown
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9)
@@ -552,9 +555,9 @@ jobs:
mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs"
mkdir -p /tmp/gh-aw/safeoutputs
mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
- cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_1051f94689a3cef8_EOF'
+ cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_4ef38fa4752d1880_EOF'
{"add_comment":{"hide_older_comments":true,"max":2},"add_labels":{"allowed":["smoke-pi"]},"create_issue":{"close_older_issues":true,"close_older_key":"smoke-pi","expires":2,"labels":["automation","testing"],"max":1},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}}
- GH_AW_SAFE_OUTPUTS_CONFIG_1051f94689a3cef8_EOF
+ GH_AW_SAFE_OUTPUTS_CONFIG_4ef38fa4752d1880_EOF
- name: Generate Safe Outputs Tools
env:
GH_AW_TOOLS_META_JSON: |
@@ -838,7 +841,7 @@ jobs:
printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
touch /tmp/gh-aw/agent-step-summary.md
(umask 177 && touch /tmp/gh-aw/agent-stdio.log)
- printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.51/awf-config.schema.json","network":{"allowDomains":["*.githubusercontent.com","*.grafana.net","*.sentry.io","api.githubcopilot.com","api.pi.ai","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","codeload.github.com","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","docs.github.com","github-cloud.githubusercontent.com","github-cloud.s3.amazonaws.com","github.blog","github.com","github.githubassets.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","lfs.github.com","objects.githubusercontent.com","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","patch-diff.githubusercontent.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000,"models":{"agent":["sonnet-6x","gpt-5.4","gpt-5","gemini-pro","haiku","any"],"antigravity":["copilot/antigravity*","google/antigravity*","gemini/antigravity*"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"auto":["large"],"claude":["agent","sonnet-6x","haiku","any"],"codex":["agent","gpt-5-codex","gpt-5","any"],"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","gpt-5.4","sonnet","gpt-5","any"],"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-pro","gemini-flash","any"],"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*","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-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-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"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.4":["copilot/gpt-5.4*","openai/gpt-5.4*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"opus":["copilot/*opus*","anthropic/*opus*"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"robotics":["copilot/*robotics*","google/*robotics*","gemini/*robotics*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["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*","gemini/gemini-*image*","copilot/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.25.51"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.51/awf-config.schema.json","network":{"allowDomains":["*.githubusercontent.com","*.grafana.net","*.sentry.io","api.githubcopilot.com","api.npms.io","api.pi.ai","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","bun.sh","cdn.jsdelivr.net","codeload.github.com","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","deb.nodesource.com","deno.land","docs.github.com","esm.sh","get.pnpm.io","github-cloud.githubusercontent.com","github-cloud.s3.amazonaws.com","github.blog","github.com","github.githubassets.com","googleapis.deno.dev","googlechromelabs.github.io","host.docker.internal","json-schema.org","json.schemastore.org","jsr.io","keyserver.ubuntu.com","lfs.github.com","nodejs.org","npm.pkg.github.com","npmjs.com","npmjs.org","objects.githubusercontent.com","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","patch-diff.githubusercontent.com","ppa.launchpad.net","raw.githubusercontent.com","registry.bower.io","registry.npmjs.com","registry.npmjs.org","registry.yarnpkg.com","repo.yarnpkg.com","s.symcb.com","s.symcd.com","security.ubuntu.com","skimdb.npmjs.com","storage.googleapis.com","telemetry.vercel.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com","www.npmjs.com","www.npmjs.org","yarnpkg.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000,"models":{"agent":["sonnet-6x","gpt-5.4","gpt-5","gemini-pro","haiku","any"],"antigravity":["copilot/antigravity*","google/antigravity*","gemini/antigravity*"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"auto":["large"],"claude":["agent","sonnet-6x","haiku","any"],"codex":["agent","gpt-5-codex","gpt-5","any"],"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","gpt-5.4","sonnet","gpt-5","any"],"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-pro","gemini-flash","any"],"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*","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-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-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"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.4":["copilot/gpt-5.4*","openai/gpt-5.4*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"opus":["copilot/*opus*","anthropic/*opus*"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"robotics":["copilot/*robotics*","google/*robotics*","gemini/*robotics*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["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*","gemini/gemini-*image*","copilot/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.25.51"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json"
cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS=""
if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
@@ -921,7 +924,7 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
- GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.grafana.net,*.sentry.io,127.0.0.1,::1,api.githubcopilot.com,api.pi.ai,api.snapcraft.io,app.renovatebot.com,appveyor.com,archive.ubuntu.com,azure.archive.ubuntu.com,badgen.net,circleci.com,codacy.com,codeclimate.com,codecov.io,codeload.github.com,coveralls.io,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,deepsource.io,docs.github.com,drone.io,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,img.shields.io,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,localhost,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,ppa.launchpad.net,raw.githubusercontent.com,readthedocs.io,readthedocs.org,registry.npmjs.org,renovatebot.com,s.symcb.com,s.symcd.com,security.ubuntu.com,semaphoreci.com,shields.io,snyk.io,sonarcloud.io,sonarqube.com,travis-ci.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.grafana.net,*.sentry.io,127.0.0.1,::1,api.githubcopilot.com,api.npms.io,api.pi.ai,api.snapcraft.io,app.renovatebot.com,appveyor.com,archive.ubuntu.com,azure.archive.ubuntu.com,badgen.net,bun.sh,cdn.jsdelivr.net,circleci.com,codacy.com,codeclimate.com,codecov.io,codeload.github.com,coveralls.io,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,deb.nodesource.com,deepsource.io,deno.land,docs.github.com,drone.io,esm.sh,get.pnpm.io,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,googleapis.deno.dev,googlechromelabs.github.io,host.docker.internal,img.shields.io,json-schema.org,json.schemastore.org,jsr.io,keyserver.ubuntu.com,lfs.github.com,localhost,nodejs.org,npm.pkg.github.com,npmjs.com,npmjs.org,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,ppa.launchpad.net,raw.githubusercontent.com,readthedocs.io,readthedocs.org,registry.bower.io,registry.npmjs.com,registry.npmjs.org,registry.yarnpkg.com,renovatebot.com,repo.yarnpkg.com,s.symcb.com,s.symcd.com,security.ubuntu.com,semaphoreci.com,shields.io,skimdb.npmjs.com,snyk.io,sonarcloud.io,sonarqube.com,storage.googleapis.com,telemetry.vercel.com,travis-ci.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.npmjs.com,www.npmjs.org,yarnpkg.com"
GITHUB_SERVER_URL: ${{ github.server_url }}
GITHUB_API_URL: ${{ github.api_url }}
with:
@@ -1338,8 +1341,6 @@ jobs:
run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.51
- name: Install Pi CLI
run: npm install --ignore-scripts -g @earendil-works/pi-coding-agent@0.75.4
- env:
- NPM_CONFIG_MIN_RELEASE_AGE: '3'
- name: Execute Pi CLI
if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
@@ -1545,7 +1546,7 @@ jobs:
env:
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
- GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.grafana.net,*.sentry.io,127.0.0.1,::1,api.githubcopilot.com,api.pi.ai,api.snapcraft.io,app.renovatebot.com,appveyor.com,archive.ubuntu.com,azure.archive.ubuntu.com,badgen.net,circleci.com,codacy.com,codeclimate.com,codecov.io,codeload.github.com,coveralls.io,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,deepsource.io,docs.github.com,drone.io,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,img.shields.io,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,localhost,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,ppa.launchpad.net,raw.githubusercontent.com,readthedocs.io,readthedocs.org,registry.npmjs.org,renovatebot.com,s.symcb.com,s.symcd.com,security.ubuntu.com,semaphoreci.com,shields.io,snyk.io,sonarcloud.io,sonarqube.com,travis-ci.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
+ GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.grafana.net,*.sentry.io,127.0.0.1,::1,api.githubcopilot.com,api.npms.io,api.pi.ai,api.snapcraft.io,app.renovatebot.com,appveyor.com,archive.ubuntu.com,azure.archive.ubuntu.com,badgen.net,bun.sh,cdn.jsdelivr.net,circleci.com,codacy.com,codeclimate.com,codecov.io,codeload.github.com,coveralls.io,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,deb.nodesource.com,deepsource.io,deno.land,docs.github.com,drone.io,esm.sh,get.pnpm.io,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,googleapis.deno.dev,googlechromelabs.github.io,host.docker.internal,img.shields.io,json-schema.org,json.schemastore.org,jsr.io,keyserver.ubuntu.com,lfs.github.com,localhost,nodejs.org,npm.pkg.github.com,npmjs.com,npmjs.org,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,ppa.launchpad.net,raw.githubusercontent.com,readthedocs.io,readthedocs.org,registry.bower.io,registry.npmjs.com,registry.npmjs.org,registry.yarnpkg.com,renovatebot.com,repo.yarnpkg.com,s.symcb.com,s.symcd.com,security.ubuntu.com,semaphoreci.com,shields.io,skimdb.npmjs.com,snyk.io,sonarcloud.io,sonarqube.com,storage.googleapis.com,telemetry.vercel.com,travis-ci.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.npmjs.com,www.npmjs.org,yarnpkg.com"
GITHUB_SERVER_URL: ${{ github.server_url }}
GITHUB_API_URL: ${{ github.api_url }}
GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":2},\"add_labels\":{\"allowed\":[\"smoke-pi\"]},\"create_issue\":{\"close_older_issues\":true,\"close_older_key\":\"smoke-pi\",\"expires\":2,\"labels\":[\"automation\",\"testing\"],\"max\":1},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}"
diff --git a/.github/workflows/smoke-pi.md b/.github/workflows/smoke-pi.md
index c448b971205..ab599f2bcac 100644
--- a/.github/workflows/smoke-pi.md
+++ b/.github/workflows/smoke-pi.md
@@ -17,6 +17,8 @@ engine:
id: pi
model: copilot/claude-sonnet-4-20250514
strict: true
+runtimes:
+ node: {}
imports:
- shared/gh.md
- shared/reporting-otlp.md
diff --git a/actions/setup/js/awf_reflect.cjs b/actions/setup/js/awf_reflect.cjs
index 2a0e0273aa2..dc82f73835e 100644
--- a/actions/setup/js/awf_reflect.cjs
+++ b/actions/setup/js/awf_reflect.cjs
@@ -26,7 +26,7 @@ const AWF_API_PROXY_REFLECT_URL = "http://api-proxy:10000/reflect";
// co-located with other AWF firewall observability data so it is included in the agent artifact.
const AWF_REFLECT_OUTPUT_PATH = "/tmp/gh-aw/sandbox/firewall/awf-reflect.json";
// Milliseconds to wait for the /reflect endpoint before giving up.
-const AWF_REFLECT_TIMEOUT_MS = 5000;
+const AWF_REFLECT_TIMEOUT_MS = 60000;
// Milliseconds to wait for each models_url fallback fetch (shorter than the main reflect timeout).
const AWF_MODELS_URL_TIMEOUT_MS = 3000;
// Gemini model name prefix stripped from model IDs in the Gemini models API response.
@@ -165,7 +165,15 @@ async function enrichReflectModels(reflectData, timeoutMs, logger) {
* logger?: (msg: string) => void,
* writeFileSync?: (path: string, data: string, options: object) => void,
* }=} options
- * @returns {Promise}
+ * @returns {Promise<{
+ * ok: boolean,
+ * reflectUrl: string,
+ * outputPath: string,
+ * bytesWritten?: number,
+ * reason?: "unexpected_status"|"timeout"|"request_failed",
+ * status?: number,
+ * error?: string,
+ * }>}
*/
async function fetchAWFReflect(options) {
const reflectUrl = (options && options.reflectUrl) || AWF_API_PROXY_REFLECT_URL;
@@ -178,7 +186,9 @@ async function fetchAWFReflect(options) {
logger(`awf-reflect: fetching ${reflectUrl} (timeout=${timeoutMs}ms)`);
const ac = new AbortController();
+ let timedOut = false;
const timer = setTimeout(() => {
+ timedOut = true;
logger(`awf-reflect: request timed out after ${timeoutMs}ms`);
ac.abort();
}, timeoutMs);
@@ -187,7 +197,13 @@ async function fetchAWFReflect(options) {
const res = await fetch(reflectUrl, { signal: ac.signal });
if (!res.ok) {
logger(`awf-reflect: unexpected status ${res.status}, skipping`);
- return;
+ return {
+ ok: false,
+ reflectUrl,
+ outputPath,
+ reason: "unexpected_status",
+ status: res.status,
+ };
}
const reflectData = await res.json();
// Attempt to fill in null models for configured providers by fetching directly
@@ -198,15 +214,35 @@ async function fetchAWFReflect(options) {
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
writeFile(outputPath, enrichedBody, { encoding: "utf8" });
logger(`awf-reflect: saved ${enrichedBody.length}B to ${outputPath}`);
+ return {
+ ok: true,
+ reflectUrl,
+ outputPath,
+ bytesWritten: enrichedBody.length,
+ };
} catch (err) {
const e = /** @type {Error} */ err;
if (e.name === "AbortError") {
- return; // already logged above
+ return {
+ ok: false,
+ reflectUrl,
+ outputPath,
+ reason: "timeout",
+ error: timedOut ? `request timed out after ${timeoutMs}ms` : e.message,
+ };
}
logger(`awf-reflect: request failed: ${e.message}`);
+ return {
+ ok: false,
+ reflectUrl,
+ outputPath,
+ reason: "request_failed",
+ error: e.message,
+ };
} finally {
clearTimeout(timer);
}
+
}
if (typeof module !== "undefined" && module.exports) {
diff --git a/actions/setup/js/awf_reflect.test.cjs b/actions/setup/js/awf_reflect.test.cjs
index a3b5a555537..72859de1c91 100644
--- a/actions/setup/js/awf_reflect.test.cjs
+++ b/actions/setup/js/awf_reflect.test.cjs
@@ -22,7 +22,7 @@ describe("awf_reflect.cjs", () => {
it("exports expected default values", () => {
expect(AWF_API_PROXY_REFLECT_URL).toBe("http://api-proxy:10000/reflect");
expect(AWF_REFLECT_OUTPUT_PATH).toBe("/tmp/gh-aw/sandbox/firewall/awf-reflect.json");
- expect(AWF_REFLECT_TIMEOUT_MS).toBe(5000);
+ expect(AWF_REFLECT_TIMEOUT_MS).toBe(60000);
expect(AWF_MODELS_URL_TIMEOUT_MS).toBe(3000);
expect(GEMINI_MODEL_NAME_PREFIX).toBe("models/");
});
@@ -198,7 +198,7 @@ describe("awf_reflect.cjs", () => {
const logs = [];
try {
- await fetchAWFReflect({
+ const result = await fetchAWFReflect({
reflectUrl: "http://api-proxy:10000/reflect",
outputPath,
timeoutMs: 3000,
@@ -206,6 +206,12 @@ describe("awf_reflect.cjs", () => {
logger: msg => logs.push(msg),
});
+ expect(result).toEqual({
+ ok: true,
+ reflectUrl: "http://api-proxy:10000/reflect",
+ outputPath,
+ bytesWritten: expect.any(Number),
+ });
const saved = JSON.parse(fs.readFileSync(outputPath, "utf8"));
expect(saved.endpoints[0].models).toEqual(["gpt-4o", "gpt-4o-mini"]);
expect(logs.some(l => l.includes("saved "))).toBe(true);
@@ -224,7 +230,13 @@ describe("awf_reflect.cjs", () => {
timeoutMs: 500,
logger: msg => logs.push(msg),
})
- ).resolves.toBeUndefined();
+ ).resolves.toEqual({
+ ok: false,
+ reflectUrl: "http://api-proxy:10000/reflect",
+ outputPath: "/tmp/gh-aw-test-noop.json",
+ reason: "request_failed",
+ error: "ECONNREFUSED",
+ });
expect(logs.some(l => l.includes("request failed"))).toBe(true);
});
@@ -238,7 +250,13 @@ describe("awf_reflect.cjs", () => {
timeoutMs: 500,
logger: msg => logs.push(msg),
})
- ).resolves.toBeUndefined();
+ ).resolves.toEqual({
+ ok: false,
+ reflectUrl: "http://api-proxy:10000/reflect",
+ outputPath: "/tmp/gh-aw-test-noop.json",
+ reason: "unexpected_status",
+ status: 503,
+ });
expect(logs.some(l => l.includes("unexpected status 503"))).toBe(true);
});
diff --git a/actions/setup/js/pi_provider.cjs b/actions/setup/js/pi_provider.cjs
index 6a449cc880e..3f597f9831f 100644
--- a/actions/setup/js/pi_provider.cjs
+++ b/actions/setup/js/pi_provider.cjs
@@ -82,6 +82,94 @@ function resolveGatewayUrl(provider) {
return `http://api-proxy:${port}`;
}
+/**
+ * Join a base URL and relative API path without duplicating slashes.
+ *
+ * @param {string} baseUrl
+ * @param {string} apiPath
+ * @returns {string}
+ */
+function joinApiUrl(baseUrl, apiPath) {
+ return `${baseUrl.replace(/\/+$/, "")}${apiPath}`;
+}
+
+/**
+ * Resolve the Pi model's inferred provider request target for logging.
+ *
+ * @param {any} model
+ * @returns {{ api: string, method: string, url: string }}
+ */
+function resolveProviderRequestTarget(model) {
+ const api = typeof model?.api === "string" && model.api ? model.api : "(unknown api)";
+ const method = "POST";
+ const baseUrl = typeof model?.baseUrl === "string" && model.baseUrl ? model.baseUrl : "";
+
+ if (!baseUrl) {
+ return { api, method, url: "(baseUrl unavailable)" };
+ }
+
+ switch (api) {
+ case "openai-completions":
+ return { api, method, url: joinApiUrl(baseUrl, "/chat/completions") };
+ case "openai-responses":
+ case "azure-openai-responses":
+ case "openai-codex-responses":
+ return { api, method, url: joinApiUrl(baseUrl, "/responses") };
+ case "anthropic":
+ case "anthropic-messages":
+ return { api, method, url: joinApiUrl(baseUrl, "/messages") };
+ case "mistral-conversations":
+ return { api, method, url: joinApiUrl(baseUrl, "/conversations") };
+ default:
+ return { api, method, url: baseUrl };
+ }
+}
+
+/**
+ * Format response header names for logs without printing sensitive values.
+ *
+ * @param {Record|undefined|null} headers
+ * @returns {string}
+ */
+function formatResponseHeaderNames(headers) {
+ const names = Object.keys(headers || {})
+ .map(name => String(name).toLowerCase())
+ .sort();
+ return names.length > 0 ? names.join(",") : "none";
+}
+
+/**
+ * Log extra context when the AWF /reflect call does not produce a snapshot.
+ *
+ * @param {{
+ * phase: string,
+ * provider: string,
+ * model: string,
+ * result: {
+ * ok: boolean,
+ * reflectUrl: string,
+ * outputPath: string,
+ * reason?: string,
+ * status?: number,
+ * error?: string,
+ * },
+ * logger: (msg: string) => void,
+ * }} params
+ * @returns {void}
+ */
+function logReflectFailure(params) {
+ const { phase, provider, model, result, logger } = params;
+ if (!result || result.ok) {
+ return;
+ }
+
+ const status = typeof result.status === "number" ? ` status=${result.status}` : "";
+ const error = result.error ? ` error=${JSON.stringify(result.error)}` : "";
+ logger(
+ `reflect_failure phase=${phase} provider=${provider || "(no provider prefix)"} model=${model || "(not set)"} url=${result.reflectUrl} output=${result.outputPath} reason=${result.reason || "unknown"}${status}${error}`
+ );
+}
+
/**
* Register a Pi provider and any aliases.
*
@@ -175,8 +263,44 @@ function registerConfiguredProviders(pi, logger) {
*/
function piProviderExtension(pi) {
const log = DEFAULT_LOGGER;
+ /** @type {{ api: string, method: string, url: string }|null} */
+ let lastProviderRequest = null;
+ /** @type {{ status: number, responseHeaders: string }|null} */
+ let lastProviderResponse = null;
registerConfiguredProviders(pi, log);
+ pi.on("before_provider_request", (_event, ctx) => {
+ lastProviderRequest = resolveProviderRequestTarget(ctx && ctx.model);
+ lastProviderResponse = null;
+ const provider = ctx?.model?.provider || "(unknown provider)";
+ const model = ctx?.model?.id || getConfiguredModel() || "(unknown model)";
+ log(`provider_request provider=${provider} model=${model} api=${lastProviderRequest.api} method=${lastProviderRequest.method} url=${lastProviderRequest.url}`);
+ });
+
+ pi.on("after_provider_response", (event, ctx) => {
+ const request = lastProviderRequest || resolveProviderRequestTarget(ctx && ctx.model);
+ lastProviderResponse = {
+ status: event.status,
+ responseHeaders: formatResponseHeaderNames(event.headers),
+ };
+ const provider = ctx?.model?.provider || "(unknown provider)";
+ const model = ctx?.model?.id || getConfiguredModel() || "(unknown model)";
+ log(`provider_response provider=${provider} model=${model} status=${event.status} method=${request.method} url=${request.url} response_headers=${lastProviderResponse.responseHeaders}`);
+ });
+
+ pi.on("message_end", event => {
+ const message = event && event.message;
+ if (message?.role !== "assistant" || message?.stopReason !== "error" || !message?.errorMessage) {
+ return;
+ }
+ const request = lastProviderRequest || { api: message.api || "(unknown api)", method: "POST", url: "(request unavailable)" };
+ const status = lastProviderResponse ? String(lastProviderResponse.status) : "no-response";
+ const responseHeaders = lastProviderResponse ? lastProviderResponse.responseHeaders : "none";
+ log(
+ `provider_error provider=${message.provider || "(unknown provider)"} model=${message.model || "(unknown model)"} api=${request.api} status=${status} method=${request.method} url=${request.url} response_headers=${responseHeaders} error=${JSON.stringify(message.errorMessage)}`
+ );
+ });
+
pi.on("agent_start", async () => {
const model = getConfiguredModel();
const provider = extractProviderFromModel(model);
@@ -196,13 +320,14 @@ function piProviderExtension(pi) {
// This is best-effort: failures are logged but do not affect the agent session.
// Skip when AWF_REFLECT_ENABLED is not "1" (e.g. sandbox.agent: false — no api-proxy running).
if (process.env.AWF_REFLECT_ENABLED === "1") {
- await fetchAWFReflect({
+ const result = await fetchAWFReflect({
reflectUrl: AWF_API_PROXY_REFLECT_URL,
outputPath: AWF_REFLECT_OUTPUT_PATH,
timeoutMs: AWF_REFLECT_TIMEOUT_MS,
modelsTimeoutMs: AWF_MODELS_URL_TIMEOUT_MS,
logger: log,
});
+ logReflectFailure({ phase: "agent_start", provider, model, result, logger: log });
}
});
@@ -211,13 +336,16 @@ function piProviderExtension(pi) {
// This is best-effort: failures are logged but do not affect the agent exit code.
// Skip when AWF_REFLECT_ENABLED is not "1" (e.g. sandbox.agent: false — no api-proxy running).
if (process.env.AWF_REFLECT_ENABLED === "1") {
- await fetchAWFReflect({
+ const model = getConfiguredModel();
+ const provider = extractProviderFromModel(model);
+ const result = await fetchAWFReflect({
reflectUrl: AWF_API_PROXY_REFLECT_URL,
outputPath: AWF_REFLECT_OUTPUT_PATH,
timeoutMs: AWF_REFLECT_TIMEOUT_MS,
modelsTimeoutMs: AWF_MODELS_URL_TIMEOUT_MS,
logger: log,
});
+ logReflectFailure({ phase: "agent_end", provider, model, result, logger: log });
}
});
}
@@ -227,3 +355,6 @@ module.exports.getConfiguredModel = getConfiguredModel;
module.exports.extractProviderFromModel = extractProviderFromModel;
module.exports.resolveGatewayUrl = resolveGatewayUrl;
module.exports.registerConfiguredProviders = registerConfiguredProviders;
+module.exports.resolveProviderRequestTarget = resolveProviderRequestTarget;
+module.exports.formatResponseHeaderNames = formatResponseHeaderNames;
+module.exports.logReflectFailure = logReflectFailure;
diff --git a/actions/setup/js/pi_provider.test.cjs b/actions/setup/js/pi_provider.test.cjs
index 4bb33733bd9..fc6c3722c03 100644
--- a/actions/setup/js/pi_provider.test.cjs
+++ b/actions/setup/js/pi_provider.test.cjs
@@ -76,6 +76,83 @@ describe("pi_provider.cjs", () => {
expect(stderrOutput.some(line => line.includes("provider=copilot model=copilot/claude-sonnet-4"))).toBe(true);
});
+ it("logs provider request and response diagnostics for inference calls", async () => {
+ process.env.GH_AW_PI_MODEL = "copilot/claude-sonnet-4";
+
+ const handlers = {};
+ const pi = {
+ registerProvider: vi.fn(),
+ on: vi.fn((event, handler) => {
+ handlers[event] = handler;
+ }),
+ };
+ const ctx = {
+ model: {
+ provider: "copilot",
+ id: "claude-sonnet-4",
+ api: "openai-completions",
+ baseUrl: "http://api-proxy:10002/v1",
+ },
+ };
+
+ module.default(pi);
+ await handlers.before_provider_request({ type: "before_provider_request", payload: {} }, ctx);
+ await handlers.after_provider_response(
+ {
+ type: "after_provider_response",
+ status: 503,
+ headers: {
+ "content-type": "application/json",
+ "x-request-id": "req-123",
+ },
+ },
+ ctx
+ );
+
+ expect(stderrOutput.some(line => line.includes("provider_request provider=copilot model=claude-sonnet-4 api=openai-completions method=POST url=http://api-proxy:10002/v1/chat/completions"))).toBe(true);
+ expect(stderrOutput.some(line => line.includes("provider_response provider=copilot model=claude-sonnet-4 status=503 method=POST url=http://api-proxy:10002/v1/chat/completions response_headers=content-type,x-request-id"))).toBe(true);
+ });
+
+ it("logs assistant inference errors with the last request target", async () => {
+ process.env.GH_AW_PI_MODEL = "copilot/claude-sonnet-4";
+
+ const handlers = {};
+ const pi = {
+ registerProvider: vi.fn(),
+ on: vi.fn((event, handler) => {
+ handlers[event] = handler;
+ }),
+ };
+ const ctx = {
+ model: {
+ provider: "copilot",
+ id: "claude-sonnet-4",
+ api: "openai-completions",
+ baseUrl: "http://api-proxy:10002/v1",
+ },
+ };
+
+ module.default(pi);
+ await handlers.before_provider_request({ type: "before_provider_request", payload: {} }, ctx);
+ await handlers.message_end({
+ type: "message_end",
+ message: {
+ role: "assistant",
+ provider: "aw-gateway",
+ model: "claude-sonnet-4",
+ api: "openai-completions",
+ stopReason: "error",
+ errorMessage: "Connection error.",
+ },
+ });
+
+ expect(
+ stderrOutput.some(line =>
+ line.includes('provider_error provider=aw-gateway model=claude-sonnet-4 api=openai-completions status=no-response method=POST url=http://api-proxy:10002/v1/chat/completions response_headers=none error="Connection error."')
+ )
+ ).toBe(true);
+ });
+
it("calls /reflect on the management port (10000) when AWF_REFLECT_ENABLED is set", async () => {
process.env.GH_AW_PI_MODEL = "copilot/claude-sonnet-4";
process.env.AWF_REFLECT_ENABLED = "1";
@@ -101,6 +178,31 @@ describe("pi_provider.cjs", () => {
expect(fetchedUrls.length).toBe(2);
});
+ it("logs reflect failure context when the /reflect call fails", async () => {
+ process.env.GH_AW_PI_MODEL = "copilot/claude-sonnet-4";
+ process.env.AWF_REFLECT_ENABLED = "1";
+ global.fetch = vi.fn().mockRejectedValue(new Error("ECONNREFUSED"));
+
+ const handlers = {};
+ const pi = {
+ registerProvider: vi.fn(),
+ on: vi.fn((event, handler) => {
+ handlers[event] = handler;
+ }),
+ };
+
+ module.default(pi);
+ await handlers.agent_start();
+
+ expect(
+ stderrOutput.some(line =>
+ line.includes(
+ 'reflect_failure phase=agent_start provider=copilot model=copilot/claude-sonnet-4 url=http://api-proxy:10000/reflect output=/tmp/gh-aw/sandbox/firewall/awf-reflect.json reason=request_failed error="ECONNREFUSED"'
+ )
+ )
+ ).toBe(true);
+ });
+
it("skips /reflect when AWF_REFLECT_ENABLED is not set", async () => {
process.env.GH_AW_PI_MODEL = "copilot/claude-sonnet-4";
delete process.env.AWF_REFLECT_ENABLED;
diff --git a/pkg/workflow/pi_engine.go b/pkg/workflow/pi_engine.go
index dd12e6e12de..e73d0e12f06 100644
--- a/pkg/workflow/pi_engine.go
+++ b/pkg/workflow/pi_engine.go
@@ -186,12 +186,14 @@ func (e *PiEngine) GetInstallationSteps(workflowData *WorkflowData) []GitHubActi
version = workflowData.EngineConfig.Version
}
- npmSteps := BuildStandardNpmEngineInstallSteps(
+ npmSteps := GenerateNpmInstallSteps(
"@earendil-works/pi-coding-agent",
version,
"Install Pi CLI",
"pi",
- workflowData,
+ true, // Include Node.js setup
+ false, // Engine CLI installs must never run install scripts
+ false, // Pi installs should not apply the default npm release-age cooldown
)
steps := BuildNpmEngineInstallStepsWithAWF(npmSteps, workflowData)
diff --git a/pkg/workflow/pi_engine_test.go b/pkg/workflow/pi_engine_test.go
index 6d6bc9b0dc3..6ddc98b6166 100644
--- a/pkg/workflow/pi_engine_test.go
+++ b/pkg/workflow/pi_engine_test.go
@@ -126,6 +126,13 @@ func TestPiEngine_GetInstallationSteps_NoCustomCommand(t *testing.T) {
}
}
assert.True(t, found, "Installation steps should install @earendil-works/pi-coding-agent")
+
+ var rendered strings.Builder
+ for _, step := range steps {
+ rendered.WriteString(strings.Join(step, "\n"))
+ rendered.WriteString("\n")
+ }
+ assert.NotContains(t, rendered.String(), "NPM_CONFIG_MIN_RELEASE_AGE", "Pi installation should not set the npm release-age cooldown")
}
func TestPiEngine_GetInstallationSteps_WithCustomCommand(t *testing.T) {
diff --git a/pkg/workflow/threat_detection_test.go b/pkg/workflow/threat_detection_test.go
index 6e00f15a2cf..d07b35bd930 100644
--- a/pkg/workflow/threat_detection_test.go
+++ b/pkg/workflow/threat_detection_test.go
@@ -1778,3 +1778,30 @@ func TestBuildDetectionEngineExecutionStepPropagatesHarnessScriptOverride(t *tes
t.Errorf("expected default harness to be replaced by custom override, got:\n%s", s)
}
}
+
+func TestBuildDetectionEngineExecutionStepOmitsPiCooldownEnv(t *testing.T) {
+ compiler := NewCompiler()
+
+ data := &WorkflowData{
+ AI: "pi",
+ EngineConfig: &EngineConfig{
+ ID: "pi",
+ },
+ SafeOutputs: &SafeOutputsConfig{
+ ThreatDetection: &ThreatDetectionConfig{},
+ },
+ }
+
+ steps := compiler.buildDetectionEngineExecutionStep(data)
+ if len(steps) == 0 {
+ t.Fatal("expected non-empty steps")
+ }
+
+ rendered := strings.Join(steps, "")
+ if !strings.Contains(rendered, "Install Pi CLI") {
+ t.Fatal("expected detection steps to include the Pi install step")
+ }
+ if strings.Contains(rendered, "NPM_CONFIG_MIN_RELEASE_AGE:") {
+ t.Fatalf("expected detection steps to omit npm cooldown env for Pi installs")
+ }
+}