From 6afe5379fb3d88f7dfcc40261f4e4ecf1263ae3e Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Mon, 20 Apr 2026 10:25:29 -0700 Subject: [PATCH 01/15] chore: bump smoke-codex mcpg to v0.2.26 Update ghcr.io/github/gh-aw-mcpg from v0.2.22 to v0.2.26 in the smoke-codex lock file to debug Codex MCP tool discovery issues. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/smoke-codex.lock.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index df7b93fc..ca0d87ca 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -1,5 +1,5 @@ # gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"73c8ff39b7e3ed578d57badefb71fc04dd5c5b2c12fc03b8d418ea44727bc627","compiler_version":"v0.68.7","strict":true,"agent_id":"codex"} -# gh-aw-manifest: {"version":1,"secrets":["CODEX_API_KEY","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","OPENAI_API_KEY","TAVILY_API_KEY"],"actions":[{"repo":"actions/cache","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"},{"repo":"actions/setup-node","sha":"53b83947a5a98c8d113130e565377fae1a50d02f","version":"v6.3.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"f52802884d655622f0a2dfd6d6a2250983c95523","version":"v0.68.7"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.23","digest":"sha256:d91d8c6263597d38da4c9fb3599ea7fed26fc6fcfebe5e92beb9711980bb25ea","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.23@sha256:d91d8c6263597d38da4c9fb3599ea7fed26fc6fcfebe5e92beb9711980bb25ea"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.23","digest":"sha256:6d8d7841a56bcb2a53fae629f9a6b9c77e80fe04af44cf753d13a6003d812120","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.23@sha256:6d8d7841a56bcb2a53fae629f9a6b9c77e80fe04af44cf753d13a6003d812120"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.23","digest":"sha256:113837034dd2cd4c96d8f00f27c910eef3e44384c13bcca2f282b6ca8b457a03","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.23@sha256:113837034dd2cd4c96d8f00f27c910eef3e44384c13bcca2f282b6ca8b457a03"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.23","digest":"sha256:989d478749707bd1e81a78bb995f0bc9b96421b1c8c087b6999a860cf05f2845","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.23@sha256:989d478749707bd1e81a78bb995f0bc9b96421b1c8c087b6999a860cf05f2845"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.2.22","digest":"sha256:5345f80d8bae180f8ec836719ca8d8ae1de60aef1bede758a4731af0af979b2f","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.2.22@sha256:5345f80d8bae180f8ec836719ca8d8ae1de60aef1bede758a4731af0af979b2f"},{"image":"ghcr.io/github/github-mcp-server:v0.32.0","digest":"sha256:2763823c63bcca718ce53850a1d7fcf2f501ec84028394f1b63ce7e9f4f9be28","pinned_image":"ghcr.io/github/github-mcp-server:v0.32.0@sha256:2763823c63bcca718ce53850a1d7fcf2f501ec84028394f1b63ce7e9f4f9be28"},{"image":"mcr.microsoft.com/playwright/mcp","digest":"sha256:7b82f29c6ef83480a97f612d53ac3fd5f30a32df3fea1e06923d4204d3532bb2","pinned_image":"mcr.microsoft.com/playwright/mcp@sha256:7b82f29c6ef83480a97f612d53ac3fd5f30a32df3fea1e06923d4204d3532bb2"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} +# gh-aw-manifest: {"version":1,"secrets":["CODEX_API_KEY","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","OPENAI_API_KEY","TAVILY_API_KEY"],"actions":[{"repo":"actions/cache","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"},{"repo":"actions/setup-node","sha":"53b83947a5a98c8d113130e565377fae1a50d02f","version":"v6.3.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"f52802884d655622f0a2dfd6d6a2250983c95523","version":"v0.68.7"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.23","digest":"sha256:d91d8c6263597d38da4c9fb3599ea7fed26fc6fcfebe5e92beb9711980bb25ea","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.23@sha256:d91d8c6263597d38da4c9fb3599ea7fed26fc6fcfebe5e92beb9711980bb25ea"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.23","digest":"sha256:6d8d7841a56bcb2a53fae629f9a6b9c77e80fe04af44cf753d13a6003d812120","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.23@sha256:6d8d7841a56bcb2a53fae629f9a6b9c77e80fe04af44cf753d13a6003d812120"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.23","digest":"sha256:113837034dd2cd4c96d8f00f27c910eef3e44384c13bcca2f282b6ca8b457a03","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.23@sha256:113837034dd2cd4c96d8f00f27c910eef3e44384c13bcca2f282b6ca8b457a03"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.23","digest":"sha256:989d478749707bd1e81a78bb995f0bc9b96421b1c8c087b6999a860cf05f2845","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.23@sha256:989d478749707bd1e81a78bb995f0bc9b96421b1c8c087b6999a860cf05f2845"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.2.26","digest":"sha256:5345f80d8bae180f8ec836719ca8d8ae1de60aef1bede758a4731af0af979b2f","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.2.26@sha256:41c20d3a366498e883648bbf0c5a23d5c0102ae931e0eaa7b79bbf8e1afab29c"},{"image":"ghcr.io/github/github-mcp-server:v0.32.0","digest":"sha256:2763823c63bcca718ce53850a1d7fcf2f501ec84028394f1b63ce7e9f4f9be28","pinned_image":"ghcr.io/github/github-mcp-server:v0.32.0@sha256:2763823c63bcca718ce53850a1d7fcf2f501ec84028394f1b63ce7e9f4f9be28"},{"image":"mcr.microsoft.com/playwright/mcp","digest":"sha256:7b82f29c6ef83480a97f612d53ac3fd5f30a32df3fea1e06923d4204d3532bb2","pinned_image":"mcr.microsoft.com/playwright/mcp@sha256:7b82f29c6ef83480a97f612d53ac3fd5f30a32df3fea1e06923d4204d3532bb2"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -53,7 +53,7 @@ # - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.23@sha256:6d8d7841a56bcb2a53fae629f9a6b9c77e80fe04af44cf753d13a6003d812120 # - ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.23@sha256:113837034dd2cd4c96d8f00f27c910eef3e44384c13bcca2f282b6ca8b457a03 # - ghcr.io/github/gh-aw-firewall/squid:0.25.23@sha256:989d478749707bd1e81a78bb995f0bc9b96421b1c8c087b6999a860cf05f2845 -# - ghcr.io/github/gh-aw-mcpg:v0.2.22@sha256:5345f80d8bae180f8ec836719ca8d8ae1de60aef1bede758a4731af0af979b2f +# - ghcr.io/github/gh-aw-mcpg:v0.2.26@sha256:41c20d3a366498e883648bbf0c5a23d5c0102ae931e0eaa7b79bbf8e1afab29c # - ghcr.io/github/github-mcp-server:v0.32.0@sha256:2763823c63bcca718ce53850a1d7fcf2f501ec84028394f1b63ce7e9f4f9be28 # - mcr.microsoft.com/playwright/mcp@sha256:7b82f29c6ef83480a97f612d53ac3fd5f30a32df3fea1e06923d4204d3532bb2 # - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f @@ -500,7 +500,7 @@ jobs: const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); await determineAutomaticLockdown(github, context, core); - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.23@sha256:d91d8c6263597d38da4c9fb3599ea7fed26fc6fcfebe5e92beb9711980bb25ea ghcr.io/github/gh-aw-firewall/api-proxy:0.25.23@sha256:6d8d7841a56bcb2a53fae629f9a6b9c77e80fe04af44cf753d13a6003d812120 ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.23@sha256:113837034dd2cd4c96d8f00f27c910eef3e44384c13bcca2f282b6ca8b457a03 ghcr.io/github/gh-aw-firewall/squid:0.25.23@sha256:989d478749707bd1e81a78bb995f0bc9b96421b1c8c087b6999a860cf05f2845 ghcr.io/github/gh-aw-mcpg:v0.2.22@sha256:5345f80d8bae180f8ec836719ca8d8ae1de60aef1bede758a4731af0af979b2f ghcr.io/github/github-mcp-server:v0.32.0@sha256:2763823c63bcca718ce53850a1d7fcf2f501ec84028394f1b63ce7e9f4f9be28 mcr.microsoft.com/playwright/mcp@sha256:7b82f29c6ef83480a97f612d53ac3fd5f30a32df3fea1e06923d4204d3532bb2 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.23@sha256:d91d8c6263597d38da4c9fb3599ea7fed26fc6fcfebe5e92beb9711980bb25ea ghcr.io/github/gh-aw-firewall/api-proxy:0.25.23@sha256:6d8d7841a56bcb2a53fae629f9a6b9c77e80fe04af44cf753d13a6003d812120 ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.23@sha256:113837034dd2cd4c96d8f00f27c910eef3e44384c13bcca2f282b6ca8b457a03 ghcr.io/github/gh-aw-firewall/squid:0.25.23@sha256:989d478749707bd1e81a78bb995f0bc9b96421b1c8c087b6999a860cf05f2845 ghcr.io/github/gh-aw-mcpg:v0.2.26@sha256:41c20d3a366498e883648bbf0c5a23d5c0102ae931e0eaa7b79bbf8e1afab29c ghcr.io/github/github-mcp-server:v0.32.0@sha256:2763823c63bcca718ce53850a1d7fcf2f501ec84028394f1b63ce7e9f4f9be28 mcr.microsoft.com/playwright/mcp@sha256:7b82f29c6ef83480a97f612d53ac3fd5f30a32df3fea1e06923d4204d3532bb2 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f - name: Write Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" @@ -768,7 +768,7 @@ jobs: export GH_AW_ENGINE="codex" DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e TAVILY_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.22' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e TAVILY_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.26' cat > "${RUNNER_TEMP}/gh-aw/mcp-config/config.toml" << GH_AW_MCP_CONFIG_153d356ac1c5c33e_EOF [history] @@ -912,7 +912,7 @@ jobs: GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} GITHUB_SERVER_URL: ${{ github.server_url }} CLI_PROXY_POLICY: '{"allow-only":{"repos":"all","min-integrity":"none"}}' - CLI_PROXY_IMAGE: 'ghcr.io/github/gh-aw-mcpg:v0.2.22' + CLI_PROXY_IMAGE: 'ghcr.io/github/gh-aw-mcpg:v0.2.26' run: | bash "${RUNNER_TEMP}/gh-aw/actions/start_cli_proxy.sh" - name: Execute Codex CLI From ffe37047a6119671926f8f9d2c6a15f823332f47 Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Mon, 20 Apr 2026 10:57:51 -0700 Subject: [PATCH 02/15] fix: inject MCP gateway connection into Codex config.toml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The compiled config.toml had backend MCP server definitions (container=, guard-policies, entrypointArgs) in mcpg gateway format that Codex doesn't understand. Codex expects 'command'+'args' (stdio) or 'url' (HTTP/SSE) fields. This caused dynamic_tool_count=0 at startup — Codex had zero MCP tools available. Fix: Before running Codex, rewrite config.toml to replace the backend server definitions with a single gateway SSE entry pointing to the already-running MCP gateway at host.docker.internal:$MCP_GATEWAY_PORT. This gives Codex access to all 31 aggregated tools (playwright, safeoutputs, etc.) through the gateway. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/smoke-codex.lock.yml | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index ca0d87ca..d1e5fdae 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -921,6 +921,32 @@ jobs: set -o pipefail mkdir -p "$CODEX_HOME/logs" && touch /tmp/gh-aw/agent-step-summary.md (umask 177 && touch /tmp/gh-aw/agent-stdio.log) + + # Fix: Rewrite Codex config.toml to connect to the MCP gateway + # The compiled config.toml has backend server definitions (container=, guard-policies) + # that Codex doesn't understand. Replace with a single gateway SSE entry. + cat > "$CODEX_HOME/config.toml" << 'CODEX_CONFIG_HEADER_EOF' + [history] + persistence = "none" + + [shell_environment_policy] + inherit = "core" + include_only = ["CODEX_API_KEY", "GH_AW_ASSETS_ALLOWED_EXTS", "GH_AW_ASSETS_BRANCH", "GH_AW_ASSETS_MAX_SIZE_KB", "GH_AW_SAFE_OUTPUTS", "GITHUB_REPOSITORY", "GITHUB_SERVER_URL", "HOME", "OPENAI_API_KEY", "PATH"] + CODEX_CONFIG_HEADER_EOF + + # Append MCP gateway connection (unquoted heredoc for variable expansion) + cat >> "$CODEX_HOME/config.toml" << CODEX_CONFIG_MCP_EOF + + [mcp_servers.gateway] + url = "http://host.docker.internal:${MCP_GATEWAY_PORT}/sse" + + [mcp_servers.gateway.headers] + Authorization = "Bearer ${MCP_GATEWAY_API_KEY}" + CODEX_CONFIG_MCP_EOF + + echo "[INFO] Codex config.toml written with MCP gateway at host.docker.internal:${MCP_GATEWAY_PORT}" + cat "$CODEX_HOME/config.toml" + # shellcheck disable=SC1003 sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env CODEX_API_KEY --exclude-env GH_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --exclude-env OPENAI_API_KEY --exclude-env TAVILY_API_KEY --allow-domains '*.githubusercontent.com,172.30.0.1,api.openai.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,cdn.playwright.dev,chatgpt.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,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,mcp.tavily.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,openai.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,playwright.download.prss.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,s.symcb.com,s.symcd.com,security.ubuntu.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --session-state-dir /tmp/gh-aw/sandbox/agent/session-state --enable-host-access --build-local --enable-api-proxy --difc-proxy-host host.docker.internal:18443 --difc-proxy-ca-cert /tmp/gh-aw/difc-proxy-tls/ca.crt \ -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && INSTRUCTION="$(cat /tmp/gh-aw/aw-prompts/prompt.txt)" && codex ${GH_AW_MODEL_AGENT_CODEX:+-c model="$GH_AW_MODEL_AGENT_CODEX" }exec -c web_search="disabled" -c fetch="disabled" --dangerously-bypass-approvals-and-sandbox --skip-git-repo-check "$INSTRUCTION"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log @@ -929,6 +955,8 @@ jobs: CODEX_HOME: /tmp/gh-aw/mcp-config GH_AW_MCP_CONFIG: ${{ runner.temp }}/gh-aw/mcp-config/config.toml GH_AW_MODEL_AGENT_CODEX: ${{ vars.GH_AW_MODEL_AGENT_CODEX || '' }} + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} From 223b39311922220a372fedbd295082bc5df45df8 Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Mon, 20 Apr 2026 11:42:38 -0700 Subject: [PATCH 03/15] ci: add API key validation step before Codex execution Adds diagnostic step to verify: - OpenAI API key validity (curl /v1/models) - AWF_GATEWAY_TOKEN matches MCP_GATEWAY_API_KEY - Gateway auth works from host (127.0.0.1) - Gateway auth works via host.docker.internal Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/smoke-codex.lock.yml | 65 +++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index d1e5fdae..60da86a9 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -915,6 +915,48 @@ jobs: CLI_PROXY_IMAGE: 'ghcr.io/github/gh-aw-mcpg:v0.2.26' run: | bash "${RUNNER_TEMP}/gh-aw/actions/start_cli_proxy.sh" + - name: Validate API keys + env: + OPENAI_API_KEY: ${{ secrets.CODEX_API_KEY || secrets.OPENAI_API_KEY }} + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + AWF_GATEWAY_TOKEN: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + run: | + set -eo pipefail + echo "::group::Validate OpenAI API key" + STATUS=$(curl -s -o /dev/null -w '%{http_code}' https://api.openai.com/v1/models \ + -H "Authorization: Bearer $OPENAI_API_KEY") + if [ "$STATUS" = "200" ]; then + echo "✅ OpenAI API key is valid (HTTP $STATUS)" + else + echo "❌ OpenAI API key check failed (HTTP $STATUS)" + fi + echo "::endgroup::" + + echo "::group::Validate MCP Gateway API key" + echo "AWF_GATEWAY_TOKEN length: ${#AWF_GATEWAY_TOKEN}" + echo "MCP_GATEWAY_API_KEY length: ${#MCP_GATEWAY_API_KEY}" + echo "Keys match: $([ "$AWF_GATEWAY_TOKEN" = "$MCP_GATEWAY_API_KEY" ] && echo 'yes' || echo 'NO')" + + # Test gateway auth directly from host + STATUS=$(curl -s -o /tmp/gw-auth-test.txt -w '%{http_code}' \ + -X POST "http://127.0.0.1:${MCP_GATEWAY_PORT}/mcp/playwright" \ + -H "Authorization: Bearer ${AWF_GATEWAY_TOKEN}" \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":1,"method":"ping"}') + echo "Gateway auth with AWF_GATEWAY_TOKEN: HTTP $STATUS" + cat /tmp/gw-auth-test.txt | head -5 + echo "" + + # Test from Docker network (simulating AWF agent container) + STATUS=$(curl -s -o /tmp/gw-auth-test2.txt -w '%{http_code}' \ + -X POST "http://host.docker.internal:${MCP_GATEWAY_PORT}/mcp/playwright" \ + -H "Authorization: Bearer ${AWF_GATEWAY_TOKEN}" \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":1,"method":"ping"}') + echo "Gateway auth via host.docker.internal: HTTP $STATUS" + cat /tmp/gw-auth-test2.txt | head -5 + echo "::endgroup::" - name: Execute Codex CLI id: agentic_execution run: | @@ -924,24 +966,32 @@ jobs: # Fix: Rewrite Codex config.toml to connect to the MCP gateway # The compiled config.toml has backend server definitions (container=, guard-policies) - # that Codex doesn't understand. Replace with a single gateway SSE entry. + # that Codex doesn't understand. Replace with per-server gateway entries using + # routed endpoints (/mcp/) and bearer token auth. cat > "$CODEX_HOME/config.toml" << 'CODEX_CONFIG_HEADER_EOF' [history] persistence = "none" [shell_environment_policy] inherit = "core" - include_only = ["CODEX_API_KEY", "GH_AW_ASSETS_ALLOWED_EXTS", "GH_AW_ASSETS_BRANCH", "GH_AW_ASSETS_MAX_SIZE_KB", "GH_AW_SAFE_OUTPUTS", "GITHUB_REPOSITORY", "GITHUB_SERVER_URL", "HOME", "OPENAI_API_KEY", "PATH"] + include_only = ["AWF_GATEWAY_TOKEN", "CODEX_API_KEY", "GH_AW_ASSETS_ALLOWED_EXTS", "GH_AW_ASSETS_BRANCH", "GH_AW_ASSETS_MAX_SIZE_KB", "GH_AW_SAFE_OUTPUTS", "GITHUB_REPOSITORY", "GITHUB_SERVER_URL", "HOME", "OPENAI_API_KEY", "PATH"] CODEX_CONFIG_HEADER_EOF - # Append MCP gateway connection (unquoted heredoc for variable expansion) + # Append MCP server entries pointing to gateway's routed endpoints + # Using unquoted heredoc so $MCP_GATEWAY_PORT expands cat >> "$CODEX_HOME/config.toml" << CODEX_CONFIG_MCP_EOF - [mcp_servers.gateway] - url = "http://host.docker.internal:${MCP_GATEWAY_PORT}/sse" + [mcp_servers.playwright] + url = "http://host.docker.internal:${MCP_GATEWAY_PORT}/mcp/playwright" + bearer_token_env_var = "AWF_GATEWAY_TOKEN" + + [mcp_servers.safeoutputs] + url = "http://host.docker.internal:${MCP_GATEWAY_PORT}/mcp/safeoutputs" + bearer_token_env_var = "AWF_GATEWAY_TOKEN" - [mcp_servers.gateway.headers] - Authorization = "Bearer ${MCP_GATEWAY_API_KEY}" + [mcp_servers.tavily] + url = "http://host.docker.internal:${MCP_GATEWAY_PORT}/mcp/tavily" + bearer_token_env_var = "AWF_GATEWAY_TOKEN" CODEX_CONFIG_MCP_EOF echo "[INFO] Codex config.toml written with MCP gateway at host.docker.internal:${MCP_GATEWAY_PORT}" @@ -957,6 +1007,7 @@ jobs: GH_AW_MODEL_AGENT_CODEX: ${{ vars.GH_AW_MODEL_AGENT_CODEX || '' }} MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + AWF_GATEWAY_TOKEN: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} From 65adbc8b4d95c70b678d42a2ccb73c74a329e260 Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Mon, 20 Apr 2026 12:52:09 -0700 Subject: [PATCH 04/15] fix: use converter-generated config instead of manual MCP config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: The gateway generates auth headers (HMAC-signed tokens) that differ from the raw MCP_GATEWAY_API_KEY. The converter script (convert_gateway_config_codex.cjs) correctly reads these from the gateway output, but writes to ${RUNNER_TEMP}/gh-aw/mcp-config/ while CODEX_HOME is /tmp/gh-aw/mcp-config — different paths. Fix: Copy the converter's output (with correct auth headers and 172.30.0.1 resolved URLs) to CODEX_HOME, prepending the shell_environment_policy that the converter doesn't generate. Also update validation step to test with the converter's auth header instead of the raw API key. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/smoke-codex.lock.yml | 100 +++++++++++-------------- 1 file changed, 43 insertions(+), 57 deletions(-) diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index 60da86a9..9fc52cc3 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -919,43 +919,35 @@ jobs: env: OPENAI_API_KEY: ${{ secrets.CODEX_API_KEY || secrets.OPENAI_API_KEY }} MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} - AWF_GATEWAY_TOKEN: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} run: | - set -eo pipefail echo "::group::Validate OpenAI API key" STATUS=$(curl -s -o /dev/null -w '%{http_code}' https://api.openai.com/v1/models \ -H "Authorization: Bearer $OPENAI_API_KEY") - if [ "$STATUS" = "200" ]; then - echo "✅ OpenAI API key is valid (HTTP $STATUS)" - else - echo "❌ OpenAI API key check failed (HTTP $STATUS)" - fi + echo "OpenAI API key: HTTP $STATUS" echo "::endgroup::" - echo "::group::Validate MCP Gateway API key" - echo "AWF_GATEWAY_TOKEN length: ${#AWF_GATEWAY_TOKEN}" - echo "MCP_GATEWAY_API_KEY length: ${#MCP_GATEWAY_API_KEY}" - echo "Keys match: $([ "$AWF_GATEWAY_TOKEN" = "$MCP_GATEWAY_API_KEY" ] && echo 'yes' || echo 'NO')" - - # Test gateway auth directly from host - STATUS=$(curl -s -o /tmp/gw-auth-test.txt -w '%{http_code}' \ - -X POST "http://127.0.0.1:${MCP_GATEWAY_PORT}/mcp/playwright" \ - -H "Authorization: Bearer ${AWF_GATEWAY_TOKEN}" \ - -H "Content-Type: application/json" \ - -d '{"jsonrpc":"2.0","id":1,"method":"ping"}') - echo "Gateway auth with AWF_GATEWAY_TOKEN: HTTP $STATUS" - cat /tmp/gw-auth-test.txt | head -5 - echo "" - - # Test from Docker network (simulating AWF agent container) - STATUS=$(curl -s -o /tmp/gw-auth-test2.txt -w '%{http_code}' \ - -X POST "http://host.docker.internal:${MCP_GATEWAY_PORT}/mcp/playwright" \ - -H "Authorization: Bearer ${AWF_GATEWAY_TOKEN}" \ - -H "Content-Type: application/json" \ - -d '{"jsonrpc":"2.0","id":1,"method":"ping"}') - echo "Gateway auth via host.docker.internal: HTTP $STATUS" - cat /tmp/gw-auth-test2.txt | head -5 + echo "::group::Validate MCP Gateway auth" + # Check converter output exists + CONVERTER_CONFIG="${RUNNER_TEMP}/gh-aw/mcp-config/config.toml" + echo "Converter config exists: $([ -f "$CONVERTER_CONFIG" ] && echo 'yes' || echo 'NO')" + if [ -f "$CONVERTER_CONFIG" ]; then + echo "Converter config contents:" + cat "$CONVERTER_CONFIG" + echo "" + # Extract auth header from converter config + AUTH_HEADER=$(grep -oP 'Authorization = "\K[^"]+' "$CONVERTER_CONFIG" | head -1) + echo "Auth header length: ${#AUTH_HEADER}" + if [ -n "$AUTH_HEADER" ]; then + STATUS=$(curl -s -o /tmp/gw-auth-test.txt -w '%{http_code}' \ + -X POST "http://127.0.0.1:${MCP_GATEWAY_PORT}/mcp/playwright" \ + -H "Authorization: $AUTH_HEADER" \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":1,"method":"ping"}') + echo "Gateway auth with converter header: HTTP $STATUS" + head -3 /tmp/gw-auth-test.txt + fi + fi echo "::endgroup::" - name: Execute Codex CLI id: agentic_execution @@ -964,37 +956,32 @@ jobs: mkdir -p "$CODEX_HOME/logs" && touch /tmp/gh-aw/agent-step-summary.md (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - # Fix: Rewrite Codex config.toml to connect to the MCP gateway - # The compiled config.toml has backend server definitions (container=, guard-policies) - # that Codex doesn't understand. Replace with per-server gateway entries using - # routed endpoints (/mcp/) and bearer token auth. - cat > "$CODEX_HOME/config.toml" << 'CODEX_CONFIG_HEADER_EOF' + # Fix: The converter (convert_gateway_config_codex.cjs) writes to + # ${RUNNER_TEMP}/gh-aw/mcp-config/config.toml but CODEX_HOME is /tmp/gh-aw/mcp-config. + # Copy the converter's output (which has correct gateway auth headers) and + # prepend the shell_environment_policy that the converter doesn't generate. + CONVERTER_CONFIG="${RUNNER_TEMP}/gh-aw/mcp-config/config.toml" + if [ -f "$CONVERTER_CONFIG" ]; then + echo "[INFO] Using converter-generated config from $CONVERTER_CONFIG" + { + echo '[shell_environment_policy]' + echo 'inherit = "core"' + echo 'include_only = ["CODEX_API_KEY", "GH_AW_ASSETS_ALLOWED_EXTS", "GH_AW_ASSETS_BRANCH", "GH_AW_ASSETS_MAX_SIZE_KB", "GH_AW_SAFE_OUTPUTS", "GITHUB_REPOSITORY", "GITHUB_SERVER_URL", "HOME", "OPENAI_API_KEY", "PATH"]' + echo '' + cat "$CONVERTER_CONFIG" + } > "$CODEX_HOME/config.toml" + else + echo "[WARN] Converter config not found at $CONVERTER_CONFIG, using fallback" + cat > "$CODEX_HOME/config.toml" << 'CODEX_CONFIG_EOF' [history] persistence = "none" [shell_environment_policy] inherit = "core" - include_only = ["AWF_GATEWAY_TOKEN", "CODEX_API_KEY", "GH_AW_ASSETS_ALLOWED_EXTS", "GH_AW_ASSETS_BRANCH", "GH_AW_ASSETS_MAX_SIZE_KB", "GH_AW_SAFE_OUTPUTS", "GITHUB_REPOSITORY", "GITHUB_SERVER_URL", "HOME", "OPENAI_API_KEY", "PATH"] - CODEX_CONFIG_HEADER_EOF - - # Append MCP server entries pointing to gateway's routed endpoints - # Using unquoted heredoc so $MCP_GATEWAY_PORT expands - cat >> "$CODEX_HOME/config.toml" << CODEX_CONFIG_MCP_EOF - - [mcp_servers.playwright] - url = "http://host.docker.internal:${MCP_GATEWAY_PORT}/mcp/playwright" - bearer_token_env_var = "AWF_GATEWAY_TOKEN" - - [mcp_servers.safeoutputs] - url = "http://host.docker.internal:${MCP_GATEWAY_PORT}/mcp/safeoutputs" - bearer_token_env_var = "AWF_GATEWAY_TOKEN" - - [mcp_servers.tavily] - url = "http://host.docker.internal:${MCP_GATEWAY_PORT}/mcp/tavily" - bearer_token_env_var = "AWF_GATEWAY_TOKEN" - CODEX_CONFIG_MCP_EOF - - echo "[INFO] Codex config.toml written with MCP gateway at host.docker.internal:${MCP_GATEWAY_PORT}" + include_only = ["CODEX_API_KEY", "GH_AW_ASSETS_ALLOWED_EXTS", "GH_AW_ASSETS_BRANCH", "GH_AW_ASSETS_MAX_SIZE_KB", "GH_AW_SAFE_OUTPUTS", "GITHUB_REPOSITORY", "GITHUB_SERVER_URL", "HOME", "OPENAI_API_KEY", "PATH"] + CODEX_CONFIG_EOF + fi + echo "[INFO] Final Codex config.toml:" cat "$CODEX_HOME/config.toml" # shellcheck disable=SC1003 @@ -1007,7 +994,6 @@ jobs: GH_AW_MODEL_AGENT_CODEX: ${{ vars.GH_AW_MODEL_AGENT_CODEX || '' }} MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} - AWF_GATEWAY_TOKEN: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} From ad82ddbaa1ab7b455cfc3b3d06a3e737beda0b5c Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Mon, 20 Apr 2026 13:16:12 -0700 Subject: [PATCH 05/15] fix: configure OpenCode to use OpenAI provider via Copilot API proxy OpenCode defaults to Google Gemini (gemini-3-pro-preview) which requires GOOGLE_GENERATIVE_AI_API_KEY. The workflow only has COPILOT_GITHUB_TOKEN routed through the api-proxy on port 10004 as OPENAI_API_KEY. Set model to openai/gpt-4.1 in the opencode.jsonc config so OpenCode uses the OpenAI provider, which routes through the Copilot API proxy. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/smoke-opencode.lock.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/smoke-opencode.lock.yml b/.github/workflows/smoke-opencode.lock.yml index 5fff1670..bd237463 100644 --- a/.github/workflows/smoke-opencode.lock.yml +++ b/.github/workflows/smoke-opencode.lock.yml @@ -763,7 +763,7 @@ jobs: umask 077 mkdir -p "$GITHUB_WORKSPACE" CONFIG="$GITHUB_WORKSPACE/opencode.jsonc" - BASE_CONFIG='{"agent":{"build":{"permissions":{"bash":"allow","edit":"allow","read":"allow","glob":"allow","grep":"allow","write":"allow","webfetch":"allow","websearch":"allow"}}}}' + BASE_CONFIG='{"model":"openai/gpt-4.1","agent":{"build":{"permissions":{"bash":"allow","edit":"allow","read":"allow","glob":"allow","grep":"allow","write":"allow","webfetch":"allow","websearch":"allow"}}}}' if [ -f "$CONFIG" ]; then MERGED=$(jq -n --argjson base "$BASE_CONFIG" --argjson existing "$(cat "$CONFIG")" '$existing * $base') echo "$MERGED" > "$CONFIG" From bea403e0f87cf7c4bcfc0771b592c2400091b1a1 Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Mon, 20 Apr 2026 13:21:41 -0700 Subject: [PATCH 06/15] fix: configure OpenCode with custom copilot-proxy provider OpenCode doesn't auto-register providers from env vars alone - it needs either /connect auth or explicit provider config. Configure a custom 'copilot-proxy' provider using @ai-sdk/openai-compatible pointing to the AWF api-proxy at port 10004, and pre-populate auth.json with the Copilot token. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/smoke-opencode.lock.yml | 43 ++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/.github/workflows/smoke-opencode.lock.yml b/.github/workflows/smoke-opencode.lock.yml index bd237463..6866fe0e 100644 --- a/.github/workflows/smoke-opencode.lock.yml +++ b/.github/workflows/smoke-opencode.lock.yml @@ -759,11 +759,44 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/start_cli_proxy.sh" - name: Write OpenCode configuration + env: + OPENAI_API_KEY: ${{ secrets.COPILOT_GITHUB_TOKEN }} run: | umask 077 mkdir -p "$GITHUB_WORKSPACE" CONFIG="$GITHUB_WORKSPACE/opencode.jsonc" - BASE_CONFIG='{"model":"openai/gpt-4.1","agent":{"build":{"permissions":{"bash":"allow","edit":"allow","read":"allow","glob":"allow","grep":"allow","write":"allow","webfetch":"allow","websearch":"allow"}}}}' + BASE_CONFIG=$(cat <<'OCEOF' + { + "model": "copilot-proxy/gpt-4.1", + "provider": { + "copilot-proxy": { + "npm": "@ai-sdk/openai-compatible", + "name": "Copilot API Proxy", + "options": { + "baseURL": "http://host.docker.internal:10004/v1" + }, + "models": { + "gpt-4.1": { "name": "GPT-4.1" } + } + } + }, + "agent": { + "build": { + "permissions": { + "bash": "allow", + "edit": "allow", + "read": "allow", + "glob": "allow", + "grep": "allow", + "write": "allow", + "webfetch": "allow", + "websearch": "allow" + } + } + } + } + OCEOF + ) if [ -f "$CONFIG" ]; then MERGED=$(jq -n --argjson base "$BASE_CONFIG" --argjson existing "$(cat "$CONFIG")" '$existing * $base') echo "$MERGED" > "$CONFIG" @@ -771,6 +804,14 @@ jobs: echo "$BASE_CONFIG" > "$CONFIG" fi chmod 600 "$CONFIG" + echo "[INFO] OpenCode config:" + cat "$CONFIG" + # Pre-populate OpenCode auth with the Copilot token for the custom provider + AUTH_DIR="$HOME/.local/share/opencode" + mkdir -p "$AUTH_DIR" + jq -n --arg key "$OPENAI_API_KEY" '{"copilot-proxy": $key}' > "$AUTH_DIR/auth.json" + chmod 600 "$AUTH_DIR/auth.json" + echo "[INFO] OpenCode auth.json written for copilot-proxy provider" - name: Execute OpenCode CLI id: agentic_execution run: | From 6272044e596918e3e319d7679d0bd3d25762b208 Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Mon, 20 Apr 2026 14:06:04 -0700 Subject: [PATCH 07/15] fix: use api-proxy internal IP for OpenCode baseURL Inside the AWF container, host.docker.internal may not resolve or may be blocked by iptables. The api-proxy is directly reachable at 172.30.0.30:10004 on the Docker network. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/smoke-opencode.lock.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/smoke-opencode.lock.yml b/.github/workflows/smoke-opencode.lock.yml index 6866fe0e..0cb34226 100644 --- a/.github/workflows/smoke-opencode.lock.yml +++ b/.github/workflows/smoke-opencode.lock.yml @@ -773,7 +773,7 @@ jobs: "npm": "@ai-sdk/openai-compatible", "name": "Copilot API Proxy", "options": { - "baseURL": "http://host.docker.internal:10004/v1" + "baseURL": "http://172.30.0.30:10004/v1" }, "models": { "gpt-4.1": { "name": "GPT-4.1" } From 1e5e81740555d81646c5825dd6d037deb15bb88a Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Mon, 20 Apr 2026 14:14:09 -0700 Subject: [PATCH 08/15] fix: add api.openai.com to OpenCode allowed domains The api-proxy port 10004 routes via OPENAI_API_KEY to api.openai.com. Squid was blocking that upstream request with 403 TCP_DENIED. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/smoke-opencode.lock.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/smoke-opencode.lock.yml b/.github/workflows/smoke-opencode.lock.yml index 0cb34226..b475e2d5 100644 --- a/.github/workflows/smoke-opencode.lock.yml +++ b/.github/workflows/smoke-opencode.lock.yml @@ -818,7 +818,7 @@ jobs: set -o pipefail (umask 177 && touch /tmp/gh-aw/agent-stdio.log) # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --allow-domains '*.githubusercontent.com,api.githubcopilot.com,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,opencode.ai,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,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --session-state-dir /tmp/gh-aw/sandbox/agent/session-state --enable-host-access --build-local --enable-api-proxy --difc-proxy-host host.docker.internal:18443 --difc-proxy-ca-cert /tmp/gh-aw/difc-proxy-tls/ca.crt \ + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --allow-domains '*.githubusercontent.com,api.githubcopilot.com,api.openai.com,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,opencode.ai,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,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --session-state-dir /tmp/gh-aw/sandbox/agent/session-state --enable-host-access --build-local --enable-api-proxy --difc-proxy-host host.docker.internal:18443 --difc-proxy-ca-cert /tmp/gh-aw/difc-proxy-tls/ca.crt \ -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && opencode run --print-logs --log-level DEBUG "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: GH_AW_MCP_CONFIG: ${{ github.workspace }}/opencode.jsonc From a8e8888fa3574a4b44e779414d818552db4314bd Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Mon, 20 Apr 2026 14:19:00 -0700 Subject: [PATCH 09/15] fix: route OpenCode via Copilot, not OpenAI Remove OPENAI_API_KEY from workflow env so the api-proxy's port 10004 falls through to the Copilot route (COPILOT_GITHUB_TOKEN) instead of routing to api.openai.com with an invalid key. The api-proxy resolveOpenCodeRoute priority is: OPENAI_API_KEY > ANTHROPIC_API_KEY > Copilot token With OPENAI_API_KEY removed, it uses the Copilot token to route to api.githubcopilot.com (already in allowed domains). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/smoke-opencode.lock.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/smoke-opencode.lock.yml b/.github/workflows/smoke-opencode.lock.yml index b475e2d5..d82fd289 100644 --- a/.github/workflows/smoke-opencode.lock.yml +++ b/.github/workflows/smoke-opencode.lock.yml @@ -760,7 +760,7 @@ jobs: bash "${RUNNER_TEMP}/gh-aw/actions/start_cli_proxy.sh" - name: Write OpenCode configuration env: - OPENAI_API_KEY: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} run: | umask 077 mkdir -p "$GITHUB_WORKSPACE" @@ -806,10 +806,11 @@ jobs: chmod 600 "$CONFIG" echo "[INFO] OpenCode config:" cat "$CONFIG" - # Pre-populate OpenCode auth with the Copilot token for the custom provider + # Pre-populate OpenCode auth — the api-proxy doesn't check incoming auth, + # so we use a placeholder. The real Copilot token is held by the api-proxy. AUTH_DIR="$HOME/.local/share/opencode" mkdir -p "$AUTH_DIR" - jq -n --arg key "$OPENAI_API_KEY" '{"copilot-proxy": $key}' > "$AUTH_DIR/auth.json" + echo '{"copilot-proxy": "placeholder-key"}' > "$AUTH_DIR/auth.json" chmod 600 "$AUTH_DIR/auth.json" echo "[INFO] OpenCode auth.json written for copilot-proxy provider" - name: Execute OpenCode CLI @@ -818,7 +819,7 @@ jobs: set -o pipefail (umask 177 && touch /tmp/gh-aw/agent-stdio.log) # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --allow-domains '*.githubusercontent.com,api.githubcopilot.com,api.openai.com,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,opencode.ai,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,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --session-state-dir /tmp/gh-aw/sandbox/agent/session-state --enable-host-access --build-local --enable-api-proxy --difc-proxy-host host.docker.internal:18443 --difc-proxy-ca-cert /tmp/gh-aw/difc-proxy-tls/ca.crt \ + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --allow-domains '*.githubusercontent.com,api.githubcopilot.com,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,opencode.ai,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,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --session-state-dir /tmp/gh-aw/sandbox/agent/session-state --enable-host-access --build-local --enable-api-proxy --difc-proxy-host host.docker.internal:18443 --difc-proxy-ca-cert /tmp/gh-aw/difc-proxy-tls/ca.crt \ -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && opencode run --print-logs --log-level DEBUG "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: GH_AW_MCP_CONFIG: ${{ github.workspace }}/opencode.jsonc @@ -826,8 +827,6 @@ jobs: GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GITHUB_WORKSPACE: ${{ github.workspace }} NO_PROXY: localhost,127.0.0.1 - OPENAI_API_KEY: ${{ secrets.COPILOT_GITHUB_TOKEN }} - OPENAI_BASE_URL: http://host.docker.internal:10004 - name: Stop CLI proxy if: always() continue-on-error: true From 15d7a2b8b0889c3de6a51dbc82b6509ebac10e2b Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Mon, 20 Apr 2026 14:23:38 -0700 Subject: [PATCH 10/15] fix: pass COPILOT_GITHUB_TOKEN to api-proxy Without COPILOT_GITHUB_TOKEN in the Execute step env, AWF doesn't pass it to the api-proxy container. The api-proxy then has no credentials and port 10004 never starts (ConnectionRefused). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/smoke-opencode.lock.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/smoke-opencode.lock.yml b/.github/workflows/smoke-opencode.lock.yml index d82fd289..f5a4b6d1 100644 --- a/.github/workflows/smoke-opencode.lock.yml +++ b/.github/workflows/smoke-opencode.lock.yml @@ -827,6 +827,7 @@ jobs: GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GITHUB_WORKSPACE: ${{ github.workspace }} NO_PROXY: localhost,127.0.0.1 + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Stop CLI proxy if: always() continue-on-error: true From dede3b2aff208763ebf447cdfef9348c70e8572e Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Mon, 20 Apr 2026 15:05:41 -0700 Subject: [PATCH 11/15] fix: remove /v1 prefix from OpenCode baseURL The Copilot API at api.githubcopilot.com uses /chat/completions (no /v1 prefix). The @ai-sdk/openai-compatible provider appends /chat/completions to baseURL, so with /v1 it was sending /v1/chat/completions which returned 404. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/smoke-opencode.lock.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/smoke-opencode.lock.yml b/.github/workflows/smoke-opencode.lock.yml index f5a4b6d1..21b30048 100644 --- a/.github/workflows/smoke-opencode.lock.yml +++ b/.github/workflows/smoke-opencode.lock.yml @@ -773,7 +773,7 @@ jobs: "npm": "@ai-sdk/openai-compatible", "name": "Copilot API Proxy", "options": { - "baseURL": "http://172.30.0.30:10004/v1" + "baseURL": "http://172.30.0.30:10004" }, "models": { "gpt-4.1": { "name": "GPT-4.1" } From 6c7df6a909c3b3b1a022e12c79d3eff87853f18d Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Mon, 20 Apr 2026 15:15:55 -0700 Subject: [PATCH 12/15] fix: add MCP gateway connection to OpenCode config OpenCode was not connecting to the MCP gateway for safe-output tools. The agent ran successfully (LLM calls worked) but never called add_comment because it had no MCP server configured. Add 'mcp.safeoutputs' config to opencode.jsonc pointing to the MCP gateway at host.docker.internal: with proper auth header. This matches how Codex connects to the gateway via HTTP MCP. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/smoke-opencode.lock.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/smoke-opencode.lock.yml b/.github/workflows/smoke-opencode.lock.yml index 21b30048..8a86db33 100644 --- a/.github/workflows/smoke-opencode.lock.yml +++ b/.github/workflows/smoke-opencode.lock.yml @@ -761,6 +761,8 @@ jobs: - name: Write OpenCode configuration env: COPILOT_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} run: | umask 077 mkdir -p "$GITHUB_WORKSPACE" @@ -797,6 +799,12 @@ jobs: } OCEOF ) + # Add MCP gateway config with runtime values (port + API key from gateway step) + MCP_CONFIG=$(jq -n \ + --arg url "http://host.docker.internal:${MCP_GATEWAY_PORT}" \ + --arg key "${MCP_GATEWAY_API_KEY}" \ + '{mcp: {safeoutputs: {type: "remote", url: $url, headers: {Authorization: $key}, timeout: 30000}}}') + BASE_CONFIG=$(echo "$BASE_CONFIG" | jq --argjson mcp "$MCP_CONFIG" '. * $mcp') if [ -f "$CONFIG" ]; then MERGED=$(jq -n --argjson base "$BASE_CONFIG" --argjson existing "$(cat "$CONFIG")" '$existing * $base') echo "$MERGED" > "$CONFIG" From f00e4c666ccd82ecac75cd346357df4e62c9d4cc Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Mon, 20 Apr 2026 15:21:28 -0700 Subject: [PATCH 13/15] fix: use routed gateway path /mcp/safeoutputs for OpenCode MCP The MCP gateway runs in routed mode with paths like /mcp/. OpenCode was connecting to the root URL (port 80) which returned 404. Fix the URL to include the server-specific route path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/smoke-opencode.lock.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/smoke-opencode.lock.yml b/.github/workflows/smoke-opencode.lock.yml index 8a86db33..2a1fcdb0 100644 --- a/.github/workflows/smoke-opencode.lock.yml +++ b/.github/workflows/smoke-opencode.lock.yml @@ -800,8 +800,9 @@ jobs: OCEOF ) # Add MCP gateway config with runtime values (port + API key from gateway step) + # Gateway uses routed mode: /mcp/ MCP_CONFIG=$(jq -n \ - --arg url "http://host.docker.internal:${MCP_GATEWAY_PORT}" \ + --arg url "http://host.docker.internal:${MCP_GATEWAY_PORT}/mcp/safeoutputs" \ --arg key "${MCP_GATEWAY_API_KEY}" \ '{mcp: {safeoutputs: {type: "remote", url: $url, headers: {Authorization: $key}, timeout: 30000}}}') BASE_CONFIG=$(echo "$BASE_CONFIG" | jq --argjson mcp "$MCP_CONFIG" '. * $mcp') From 769cd3d1b688dfe7a6395d5a443367b11beda574 Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Mon, 20 Apr 2026 15:31:41 -0700 Subject: [PATCH 14/15] fix: add external_directory/mcp permissions and instructions for OpenCode Three fixes: 1. Add 'external_directory: allow' - prevents auto-reject of file creation in /tmp when running in non-interactive 'run' mode 2. Add 'mcp: allow' - explicitly allow MCP tool calls 3. Add 'instructions' with explicit guidance to use safeoutputs MCP tools (add_comment) instead of gh CLI for GitHub writes The model was completing after one turn of bash calls without ever calling the MCP safe-output tools (add_comment). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/smoke-opencode.lock.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/smoke-opencode.lock.yml b/.github/workflows/smoke-opencode.lock.yml index 2a1fcdb0..f2153ee0 100644 --- a/.github/workflows/smoke-opencode.lock.yml +++ b/.github/workflows/smoke-opencode.lock.yml @@ -792,10 +792,15 @@ jobs: "grep": "allow", "write": "allow", "webfetch": "allow", - "websearch": "allow" + "websearch": "allow", + "mcp": "allow", + "external_directory": "allow" } } - } + }, + "instructions": [ + "IMPORTANT: You have MCP tools from the 'safeoutputs' server. Use the add_comment MCP tool to post comments on PRs. Do NOT use gh CLI for writing. You MUST call the add_comment MCP tool before finishing." + ] } OCEOF ) From b0522e59cbbcb0c2878c7e43ea02cfce3d443221 Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Mon, 20 Apr 2026 15:36:46 -0700 Subject: [PATCH 15/15] fix: use 'permission' not 'permissions' in OpenCode agent config The OpenCode agent config field is 'permission' (singular) not 'permissions' (plural). The wrong key was silently moved to 'options' by the normalize() function, so external_directory permission was never applied. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/smoke-opencode.lock.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/smoke-opencode.lock.yml b/.github/workflows/smoke-opencode.lock.yml index f2153ee0..65d867cd 100644 --- a/.github/workflows/smoke-opencode.lock.yml +++ b/.github/workflows/smoke-opencode.lock.yml @@ -784,7 +784,7 @@ jobs: }, "agent": { "build": { - "permissions": { + "permission": { "bash": "allow", "edit": "allow", "read": "allow", @@ -793,7 +793,6 @@ jobs: "write": "allow", "webfetch": "allow", "websearch": "allow", - "mcp": "allow", "external_directory": "allow" } }