From e89e6d0bfaf484de2c17ad8598faefe0b26b3763 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 13:31:13 +0000 Subject: [PATCH 1/5] Initial plan From 5e4fce3afd6568b2a75575f9bd2234eedc3520b6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 13:43:09 +0000 Subject: [PATCH 2/5] fix: handle missing .copilot mount source for non-standard HOME Agent-Logs-Url: https://github.com/github/gh-aw-firewall/sessions/69938822-097d-4d71-891e-d8279a36271c --- docs/environment.md | 6 ++++++ src/docker-manager.test.ts | 32 ++++++++++++++++++++++++++++++++ src/docker-manager.ts | 15 ++++++++++++++- 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/docs/environment.md b/docs/environment.md index 946f42af..2b3e6115 100644 --- a/docs/environment.md +++ b/docs/environment.md @@ -25,6 +25,12 @@ When using `sudo -E`, these host variables are automatically passed: `GITHUB_TOK The following are always set/overridden: `PATH` (container values). +### Self-hosted runner home directory support + +AWF derives the effective home directory at runtime from the host environment (`$HOME`, with sudo-aware handling), not from a hardcoded `/home/runner` path. + +This means self-hosted Linux runners with non-standard service-account homes are supported, as long as `$HOME` is set correctly before invoking `awf`. + Variables from `--env` flags override everything else. **Proxy variables set automatically:** `HTTP_PROXY`, `HTTPS_PROXY`, and `https_proxy` are always set to point to the Squid proxy (`http://172.30.0.10:3128`). Note that lowercase `http_proxy` is intentionally **not** set — some curl builds on Ubuntu 22.04 ignore uppercase `HTTP_PROXY` for HTTP URLs (httpoxy mitigation), so HTTP traffic falls through to iptables DNAT interception instead. iptables DNAT serves as a defense-in-depth fallback for both HTTP and HTTPS. diff --git a/src/docker-manager.test.ts b/src/docker-manager.test.ts index 659d2318..22dc4fc0 100644 --- a/src/docker-manager.test.ts +++ b/src/docker-manager.test.ts @@ -878,6 +878,38 @@ describe('docker-manager', () => { expect(volumes).toContain(`/tmp/awf-test/agent-logs:/host${homeDir}/.copilot/logs:rw`); }); + it('should support non-standard HOME and create missing .copilot mount source', () => { + const fakeHome = fs.mkdtempSync(path.join(os.tmpdir(), 'awf-home-')); + const originalHome = process.env.HOME; + const originalSudoUser = process.env.SUDO_USER; + delete process.env.SUDO_USER; + process.env.HOME = fakeHome; + + try { + const copilotDir = path.join(fakeHome, '.copilot'); + expect(fs.existsSync(copilotDir)).toBe(false); + + const result = generateDockerCompose(mockConfig, mockNetworkConfig); + const volumes = result.services.agent.volumes as string[]; + + expect(fs.existsSync(copilotDir)).toBe(true); + expect(volumes).toContain(`${mockConfig.workDir}-chroot-home:/host${fakeHome}:rw`); + expect(volumes).toContain(`${fakeHome}/.copilot:/host${fakeHome}/.copilot:rw`); + } finally { + if (originalHome !== undefined) { + process.env.HOME = originalHome; + } else { + delete process.env.HOME; + } + if (originalSudoUser !== undefined) { + process.env.SUDO_USER = originalSudoUser; + } else { + delete process.env.SUDO_USER; + } + fs.rmSync(fakeHome, { recursive: true, force: true }); + } + }); + it('should use sessionStateDir when specified for chroot mounts', () => { const configWithSessionDir = { ...mockConfig, sessionStateDir: '/custom/session-state' }; const result = generateDockerCompose(configWithSessionDir, mockNetworkConfig); diff --git a/src/docker-manager.ts b/src/docker-manager.ts index b127c59e..6b5377bd 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -1164,7 +1164,20 @@ export function generateDockerCompose( // Mount ~/.copilot for Copilot CLI (package extraction, MCP config, etc.) // This is safe as ~/.copilot contains only Copilot CLI state, not credentials. // Auth tokens are in COPILOT_GITHUB_TOKEN env var (handled by API proxy sidecar). - agentVolumes.push(`${effectiveHome}/.copilot:/host${effectiveHome}/.copilot:rw`); + const copilotHomeDir = path.join(effectiveHome, '.copilot'); + let shouldMountCopilotDir = fs.existsSync(copilotHomeDir); + if (!shouldMountCopilotDir) { + try { + fs.mkdirSync(copilotHomeDir, { recursive: true }); + shouldMountCopilotDir = true; + logger.debug(`Created missing Copilot directory for bind mount: ${copilotHomeDir}`); + } catch (error) { + logger.warn(`Skipping ~/.copilot host bind mount because directory could not be created: ${copilotHomeDir} (${error instanceof Error ? error.message : String(error)})`); + } + } + if (shouldMountCopilotDir) { + agentVolumes.push(`${copilotHomeDir}:/host${effectiveHome}/.copilot:rw`); + } // Overlay session-state and logs from AWF workDir so events.jsonl and logs are // captured in the workDir instead of written to the host's ~/.copilot. From 17b6eeea820fe23241c1665704afe9dd7b19b658 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 13:45:39 +0000 Subject: [PATCH 3/5] refactor: simplify .copilot mount existence handling Agent-Logs-Url: https://github.com/github/gh-aw-firewall/sessions/69938822-097d-4d71-891e-d8279a36271c --- src/docker-manager.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/docker-manager.ts b/src/docker-manager.ts index 6b5377bd..cdb90d27 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -1165,17 +1165,15 @@ export function generateDockerCompose( // This is safe as ~/.copilot contains only Copilot CLI state, not credentials. // Auth tokens are in COPILOT_GITHUB_TOKEN env var (handled by API proxy sidecar). const copilotHomeDir = path.join(effectiveHome, '.copilot'); - let shouldMountCopilotDir = fs.existsSync(copilotHomeDir); - if (!shouldMountCopilotDir) { + if (!fs.existsSync(copilotHomeDir)) { try { fs.mkdirSync(copilotHomeDir, { recursive: true }); - shouldMountCopilotDir = true; logger.debug(`Created missing Copilot directory for bind mount: ${copilotHomeDir}`); } catch (error) { logger.warn(`Skipping ~/.copilot host bind mount because directory could not be created: ${copilotHomeDir} (${error instanceof Error ? error.message : String(error)})`); } } - if (shouldMountCopilotDir) { + if (fs.existsSync(copilotHomeDir)) { agentVolumes.push(`${copilotHomeDir}:/host${effectiveHome}/.copilot:rw`); } From f84cfc8cb8227695638a67f7878c8f2db85b2335 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 13:53:30 +0000 Subject: [PATCH 4/5] chore: refine warnings and HOME compatibility test wording Agent-Logs-Url: https://github.com/github/gh-aw-firewall/sessions/69938822-097d-4d71-891e-d8279a36271c --- src/docker-manager.test.ts | 3 +-- src/docker-manager.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/docker-manager.test.ts b/src/docker-manager.test.ts index 22dc4fc0..eb52ca07 100644 --- a/src/docker-manager.test.ts +++ b/src/docker-manager.test.ts @@ -878,7 +878,7 @@ describe('docker-manager', () => { expect(volumes).toContain(`/tmp/awf-test/agent-logs:/host${homeDir}/.copilot/logs:rw`); }); - it('should support non-standard HOME and create missing .copilot mount source', () => { + it('should create missing .copilot directory and mount it when using non-standard HOME path', () => { const fakeHome = fs.mkdtempSync(path.join(os.tmpdir(), 'awf-home-')); const originalHome = process.env.HOME; const originalSudoUser = process.env.SUDO_USER; @@ -893,7 +893,6 @@ describe('docker-manager', () => { const volumes = result.services.agent.volumes as string[]; expect(fs.existsSync(copilotDir)).toBe(true); - expect(volumes).toContain(`${mockConfig.workDir}-chroot-home:/host${fakeHome}:rw`); expect(volumes).toContain(`${fakeHome}/.copilot:/host${fakeHome}/.copilot:rw`); } finally { if (originalHome !== undefined) { diff --git a/src/docker-manager.ts b/src/docker-manager.ts index cdb90d27..bd54b6ff 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -1170,7 +1170,7 @@ export function generateDockerCompose( fs.mkdirSync(copilotHomeDir, { recursive: true }); logger.debug(`Created missing Copilot directory for bind mount: ${copilotHomeDir}`); } catch (error) { - logger.warn(`Skipping ~/.copilot host bind mount because directory could not be created: ${copilotHomeDir} (${error instanceof Error ? error.message : String(error)})`); + logger.warn(`Failed to create ~/.copilot directory at ${copilotHomeDir}; skipping host bind mount. Copilot CLI package extraction and persisted host MCP config may be unavailable. Error: ${error instanceof Error ? error.message : String(error)}`); } } if (fs.existsSync(copilotHomeDir)) { From be2d8e8ab606fcc77b9f65f1200082b8ff06c1cb Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Mon, 20 Apr 2026 07:57:26 -0700 Subject: [PATCH 5/5] Update src/docker-manager.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/docker-manager.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/docker-manager.ts b/src/docker-manager.ts index bd54b6ff..e903aad0 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -1165,16 +1165,15 @@ export function generateDockerCompose( // This is safe as ~/.copilot contains only Copilot CLI state, not credentials. // Auth tokens are in COPILOT_GITHUB_TOKEN env var (handled by API proxy sidecar). const copilotHomeDir = path.join(effectiveHome, '.copilot'); - if (!fs.existsSync(copilotHomeDir)) { + if (fs.existsSync(copilotHomeDir)) { try { - fs.mkdirSync(copilotHomeDir, { recursive: true }); - logger.debug(`Created missing Copilot directory for bind mount: ${copilotHomeDir}`); + fs.accessSync(copilotHomeDir, fs.constants.R_OK | fs.constants.W_OK); + agentVolumes.push(`${copilotHomeDir}:/host${effectiveHome}/.copilot:rw`); } catch (error) { - logger.warn(`Failed to create ~/.copilot directory at ${copilotHomeDir}; skipping host bind mount. Copilot CLI package extraction and persisted host MCP config may be unavailable. Error: ${error instanceof Error ? error.message : String(error)}`); + logger.warn(`Cannot access ~/.copilot directory at ${copilotHomeDir}; skipping host bind mount. Copilot CLI package extraction and persisted host MCP config may be unavailable. Error: ${error instanceof Error ? error.message : String(error)}`); } - } - if (fs.existsSync(copilotHomeDir)) { - agentVolumes.push(`${copilotHomeDir}:/host${effectiveHome}/.copilot:rw`); + } else { + logger.debug(`~/.copilot directory does not exist at ${copilotHomeDir}; skipping optional host bind mount.`); } // Overlay session-state and logs from AWF workDir so events.jsonl and logs are