From 1444acca3fe71d0a0f7735f012db7a79060f3239 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Apr 2026 02:38:44 +0000 Subject: [PATCH 1/6] Initial plan From d4142580631dac39004056b59dafb189eb785d27 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Apr 2026 02:43:27 +0000 Subject: [PATCH 2/6] fix: make node available and validated for copilot chroot startup Agent-Logs-Url: https://github.com/github/gh-aw-firewall/sessions/345ed70b-1477-4fae-bb25-801df1d3fab2 --- containers/agent/entrypoint.sh | 10 ++++++++++ src/docker-manager.test.ts | 26 ++++++++++++++++++++++++-- src/docker-manager.ts | 15 ++++++++++++++- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/containers/agent/entrypoint.sh b/containers/agent/entrypoint.sh index ea68a68b..df64066e 100644 --- a/containers/agent/entrypoint.sh +++ b/containers/agent/entrypoint.sh @@ -706,6 +706,16 @@ AWFEOF echo 'fi' >> "/host${SCRIPT_FILE}" echo 'mkdir -p "$NPM_CONFIG_PREFIX/bin" 2>/dev/null' >> "/host${SCRIPT_FILE}" echo 'export PATH="$NPM_CONFIG_PREFIX/bin:$PATH"' >> "/host${SCRIPT_FILE}" + if [ "${AWF_REQUIRE_NODE:-}" = "1" ]; then + cat >> "/host${SCRIPT_FILE}" << 'AWFEOF' +if ! command -v node >/dev/null 2>&1; then + echo "[entrypoint][ERROR] Copilot CLI requires Node.js, but 'node' is not available inside AWF chroot." + echo "[entrypoint][ERROR] Ensure Node.js is installed on the runner and reachable from PATH inside the chroot." + echo "[entrypoint][ERROR] Common locations: /opt/hostedtoolcache/... or \$HOME/.nvm/..." + exit 127 +fi +AWFEOF + fi # Append the actual command arguments # Docker CMD passes commands as ['/bin/bash', '-c', 'command_string']. # Instead of writing the full [bash, -c, cmd] via printf '%q' (which creates diff --git a/src/docker-manager.test.ts b/src/docker-manager.test.ts index 7663f381..37509a7f 100644 --- a/src/docker-manager.test.ts +++ b/src/docker-manager.test.ts @@ -857,7 +857,7 @@ describe('docker-manager', () => { expect(volumes).toContain(`${workspaceDir}:/host${workspaceDir}:rw`); }); - it('should mount Rust toolchain, npm cache, and CLI state directories', () => { + it('should mount Rust toolchain, Node/npm caches, and CLI state directories', () => { const result = generateDockerCompose(mockConfig, mockNetworkConfig); const agent = result.services.agent; const volumes = agent.volumes as string[]; @@ -868,6 +868,8 @@ describe('docker-manager', () => { expect(volumes).toContain(`${homeDir}/.rustup:/host${homeDir}/.rustup:rw`); // npm cache expect(volumes).toContain(`${homeDir}/.npm:/host${homeDir}/.npm:rw`); + // nvm-managed Node.js cache/installations + expect(volumes).toContain(`${homeDir}/.nvm:/host${homeDir}/.nvm:rw`); // CLI state directories expect(volumes).toContain(`${homeDir}/.claude:/host${homeDir}/.claude:rw`); expect(volumes).toContain(`${homeDir}/.anthropic:/host${homeDir}/.anthropic:rw`); @@ -944,6 +946,26 @@ describe('docker-manager', () => { expect(environment.AWF_CHROOT_ENABLED).toBe('true'); }); + it('should set AWF_REQUIRE_NODE when running Copilot CLI command', () => { + const result = generateDockerCompose( + { ...mockConfig, agentCommand: 'copilot --version' }, + mockNetworkConfig, + ); + const environment = result.services.agent.environment as Record; + + expect(environment.AWF_REQUIRE_NODE).toBe('1'); + }); + + it('should not set AWF_REQUIRE_NODE for non-Copilot commands', () => { + const result = generateDockerCompose( + { ...mockConfig, agentCommand: 'echo test' }, + mockNetworkConfig, + ); + const environment = result.services.agent.environment as Record; + + expect(environment.AWF_REQUIRE_NODE).toBeUndefined(); + }); + it('should pass GOROOT, CARGO_HOME, RUSTUP_HOME, JAVA_HOME, DOTNET_ROOT, BUN_INSTALL to container when env vars are set', () => { const originalGoroot = process.env.GOROOT; const originalCargoHome = process.env.CARGO_HOME; @@ -3641,7 +3663,7 @@ describe('docker-manager', () => { // Verify chroot home subdirectories were created const expectedDirs = [ '.copilot', '.cache', '.config', '.local', - '.anthropic', '.claude', '.gemini', '.cargo', '.rustup', '.npm', + '.anthropic', '.claude', '.gemini', '.cargo', '.rustup', '.npm', '.nvm', ]; for (const dir of expectedDirs) { expect(fs.existsSync(path.join(fakeHome, dir))).toBe(true); diff --git a/src/docker-manager.ts b/src/docker-manager.ts index e60deb22..52519d5d 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -815,6 +815,16 @@ export function generateDockerCompose( AWF_ONE_SHOT_TOKENS: 'COPILOT_GITHUB_TOKEN,GITHUB_TOKEN,GH_TOKEN,GITHUB_API_TOKEN,GITHUB_PAT,GH_ACCESS_TOKEN,OPENAI_API_KEY,OPENAI_KEY,ANTHROPIC_API_KEY,CLAUDE_API_KEY,CODEX_API_KEY,COPILOT_API_KEY,COPILOT_PROVIDER_API_KEY', }; + // Copilot CLI requires Node.js. Ask the agent entrypoint to fail fast with a + // clear diagnostic if node is not reachable inside the chroot before startup. + if ( + config.copilotGithubToken + || config.copilotApiKey + || /\bcopilot\b/.test(config.agentCommand) + ) { + environment.AWF_REQUIRE_NODE = '1'; + } + // When api-proxy is enabled with Copilot, set placeholder tokens early // so --env-all won't override them with real values from host environment if (config.enableApiProxy && config.copilotGithubToken) { @@ -1222,6 +1232,9 @@ export function generateDockerCompose( // npm requires write access to ~/.npm for caching packages and writing logs agentVolumes.push(`${effectiveHome}/.npm:/host${effectiveHome}/.npm:rw`); + // Mount ~/.nvm for Node.js installations managed by nvm on self-hosted runners + agentVolumes.push(`${effectiveHome}/.nvm:/host${effectiveHome}/.nvm:rw`); + // Minimal /etc - only what's needed for runtime // Note: /etc/shadow is NOT mounted (contains password hashes) agentVolumes.push( @@ -2202,7 +2215,7 @@ export async function writeConfigs(config: WrapperConfig): Promise { // Ensure source directories for subdirectory mounts exist with correct ownership const chrootHomeDirs = [ '.copilot', '.cache', '.config', '.local', - '.anthropic', '.claude', '.gemini', '.cargo', '.rustup', '.npm', + '.anthropic', '.claude', '.gemini', '.cargo', '.rustup', '.npm', '.nvm', ]; for (const dir of chrootHomeDirs) { const dirPath = path.join(effectiveHome, dir); From 91a29530b6fa6bdd20b01fa3383bc15a6f6010c0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Apr 2026 02:45:05 +0000 Subject: [PATCH 3/6] fix: harden copilot node preflight detection and diagnostics Agent-Logs-Url: https://github.com/github/gh-aw-firewall/sessions/345ed70b-1477-4fae-bb25-801df1d3fab2 --- containers/agent/entrypoint.sh | 3 ++- src/docker-manager.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/containers/agent/entrypoint.sh b/containers/agent/entrypoint.sh index df64066e..75e28e98 100644 --- a/containers/agent/entrypoint.sh +++ b/containers/agent/entrypoint.sh @@ -711,7 +711,8 @@ AWFEOF if ! command -v node >/dev/null 2>&1; then echo "[entrypoint][ERROR] Copilot CLI requires Node.js, but 'node' is not available inside AWF chroot." echo "[entrypoint][ERROR] Ensure Node.js is installed on the runner and reachable from PATH inside the chroot." - echo "[entrypoint][ERROR] Common locations: /opt/hostedtoolcache/... or \$HOME/.nvm/..." + echo "[entrypoint][ERROR] Common locations: /opt/hostedtoolcache/... or \$HOME/.nvm/... (bind-mounted by AWF)." + echo "[entrypoint][ERROR] Verify \$HOME/.nvm exists on the host and contains an installed Node.js version." exit 127 fi AWFEOF diff --git a/src/docker-manager.ts b/src/docker-manager.ts index 52519d5d..b136c22e 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -820,7 +820,7 @@ export function generateDockerCompose( if ( config.copilotGithubToken || config.copilotApiKey - || /\bcopilot\b/.test(config.agentCommand) + || /\bcopilot\b/i.test(config.agentCommand) ) { environment.AWF_REQUIRE_NODE = '1'; } From bc3841906cae32baae2773acbdda2023e3a52257 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Apr 2026 02:46:43 +0000 Subject: [PATCH 4/6] fix: avoid broad copilot matching in node preflight gate Agent-Logs-Url: https://github.com/github/gh-aw-firewall/sessions/345ed70b-1477-4fae-bb25-801df1d3fab2 --- containers/agent/entrypoint.sh | 4 ++-- src/docker-manager.ts | 8 +++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/containers/agent/entrypoint.sh b/containers/agent/entrypoint.sh index 75e28e98..76e13c10 100644 --- a/containers/agent/entrypoint.sh +++ b/containers/agent/entrypoint.sh @@ -711,8 +711,8 @@ AWFEOF if ! command -v node >/dev/null 2>&1; then echo "[entrypoint][ERROR] Copilot CLI requires Node.js, but 'node' is not available inside AWF chroot." echo "[entrypoint][ERROR] Ensure Node.js is installed on the runner and reachable from PATH inside the chroot." - echo "[entrypoint][ERROR] Common locations: /opt/hostedtoolcache/... or \$HOME/.nvm/... (bind-mounted by AWF)." - echo "[entrypoint][ERROR] Verify \$HOME/.nvm exists on the host and contains an installed Node.js version." + echo "[entrypoint][ERROR] Common locations: /opt/hostedtoolcache/... or $HOME/.nvm/... (bind-mounted by AWF)." + echo "[entrypoint][ERROR] Verify $HOME/.nvm exists on the host and contains an installed Node.js version." exit 127 fi AWFEOF diff --git a/src/docker-manager.ts b/src/docker-manager.ts index b136c22e..07727954 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -817,11 +817,9 @@ export function generateDockerCompose( // Copilot CLI requires Node.js. Ask the agent entrypoint to fail fast with a // clear diagnostic if node is not reachable inside the chroot before startup. - if ( - config.copilotGithubToken - || config.copilotApiKey - || /\bcopilot\b/i.test(config.agentCommand) - ) { + const commandExecutable = config.agentCommand.trim().split(/\s+/, 1)[0] || ''; + const isCopilotCommand = /(^|\/)copilot$/i.test(commandExecutable); + if (config.copilotGithubToken || config.copilotApiKey || isCopilotCommand) { environment.AWF_REQUIRE_NODE = '1'; } From 1f1dcf5855f5ffa9b5fc58fdc6ec3bbe2aac2009 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Apr 2026 02:48:26 +0000 Subject: [PATCH 5/6] fix: tighten copilot executable detection for node preflight Agent-Logs-Url: https://github.com/github/gh-aw-firewall/sessions/345ed70b-1477-4fae-bb25-801df1d3fab2 --- containers/agent/entrypoint.sh | 4 ++-- src/docker-manager.ts | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/containers/agent/entrypoint.sh b/containers/agent/entrypoint.sh index 76e13c10..c135b698 100644 --- a/containers/agent/entrypoint.sh +++ b/containers/agent/entrypoint.sh @@ -711,8 +711,8 @@ AWFEOF if ! command -v node >/dev/null 2>&1; then echo "[entrypoint][ERROR] Copilot CLI requires Node.js, but 'node' is not available inside AWF chroot." echo "[entrypoint][ERROR] Ensure Node.js is installed on the runner and reachable from PATH inside the chroot." - echo "[entrypoint][ERROR] Common locations: /opt/hostedtoolcache/... or $HOME/.nvm/... (bind-mounted by AWF)." - echo "[entrypoint][ERROR] Verify $HOME/.nvm exists on the host and contains an installed Node.js version." + echo "[entrypoint][ERROR] If using setup-node or nvm, verify the install path is present and bind-mounted into /host." + echo "[entrypoint][ERROR] Example locations include /opt/hostedtoolcache/... and $HOME/.nvm/..." exit 127 fi AWFEOF diff --git a/src/docker-manager.ts b/src/docker-manager.ts index 07727954..812c9187 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -818,7 +818,8 @@ export function generateDockerCompose( // Copilot CLI requires Node.js. Ask the agent entrypoint to fail fast with a // clear diagnostic if node is not reachable inside the chroot before startup. const commandExecutable = config.agentCommand.trim().split(/\s+/, 1)[0] || ''; - const isCopilotCommand = /(^|\/)copilot$/i.test(commandExecutable); + const commandExecutableBase = path.posix.basename(commandExecutable.replace(/\\/g, '/')); + const isCopilotCommand = commandExecutableBase.toLowerCase() === 'copilot'; if (config.copilotGithubToken || config.copilotApiKey || isCopilotCommand) { environment.AWF_REQUIRE_NODE = '1'; } From 2ffe7f2462fa141f2e4ca98bf68aafb5bf6f4268 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Apr 2026 15:21:46 +0000 Subject: [PATCH 6/6] fix: cover copilot auth node preflight and stderr diagnostics Agent-Logs-Url: https://github.com/github/gh-aw-firewall/sessions/b0b3e2e5-02f9-4241-ab98-9abfb180b848 --- containers/agent/entrypoint.sh | 8 ++++---- src/docker-manager.test.ts | 13 +++++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/containers/agent/entrypoint.sh b/containers/agent/entrypoint.sh index c135b698..cfa307b0 100644 --- a/containers/agent/entrypoint.sh +++ b/containers/agent/entrypoint.sh @@ -709,10 +709,10 @@ AWFEOF if [ "${AWF_REQUIRE_NODE:-}" = "1" ]; then cat >> "/host${SCRIPT_FILE}" << 'AWFEOF' if ! command -v node >/dev/null 2>&1; then - echo "[entrypoint][ERROR] Copilot CLI requires Node.js, but 'node' is not available inside AWF chroot." - echo "[entrypoint][ERROR] Ensure Node.js is installed on the runner and reachable from PATH inside the chroot." - echo "[entrypoint][ERROR] If using setup-node or nvm, verify the install path is present and bind-mounted into /host." - echo "[entrypoint][ERROR] Example locations include /opt/hostedtoolcache/... and $HOME/.nvm/..." + echo "[entrypoint][ERROR] Copilot CLI requires Node.js, but 'node' is not available inside AWF chroot." >&2 + echo "[entrypoint][ERROR] Ensure Node.js is installed on the runner and reachable from PATH inside the chroot." >&2 + echo "[entrypoint][ERROR] If using setup-node or nvm, verify the install path is present and bind-mounted into /host." >&2 + echo "[entrypoint][ERROR] Example locations include /opt/hostedtoolcache/... and $HOME/.nvm/..." >&2 exit 127 fi AWFEOF diff --git a/src/docker-manager.test.ts b/src/docker-manager.test.ts index 37509a7f..7801c05f 100644 --- a/src/docker-manager.test.ts +++ b/src/docker-manager.test.ts @@ -956,6 +956,19 @@ describe('docker-manager', () => { expect(environment.AWF_REQUIRE_NODE).toBe('1'); }); + it.each([ + { copilotGithubToken: 'ghu_test_token' }, + { copilotApiKey: 'cpat_test_key' }, + ])('should set AWF_REQUIRE_NODE when Copilot auth config is present: %o', (copilotConfig) => { + const result = generateDockerCompose( + { ...mockConfig, agentCommand: 'echo test', ...copilotConfig }, + mockNetworkConfig, + ); + const environment = result.services.agent.environment as Record; + + expect(environment.AWF_REQUIRE_NODE).toBe('1'); + }); + it('should not set AWF_REQUIRE_NODE for non-Copilot commands', () => { const result = generateDockerCompose( { ...mockConfig, agentCommand: 'echo test' },