Skip to content

Commit aac083b

Browse files
CopilotlpcoxCopilot
authored
Handle non-standard runner HOME and missing ~/.copilot in agent mount setup (#2114)
* Initial plan * 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 * refactor: simplify .copilot mount existence handling Agent-Logs-Url: https://github.com/github/gh-aw-firewall/sessions/69938822-097d-4d71-891e-d8279a36271c * chore: refine warnings and HOME compatibility test wording Agent-Logs-Url: https://github.com/github/gh-aw-firewall/sessions/69938822-097d-4d71-891e-d8279a36271c * Update src/docker-manager.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Landon Cox <landon.cox@microsoft.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 690912a commit aac083b

File tree

3 files changed

+48
-1
lines changed

3 files changed

+48
-1
lines changed

docs/environment.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ When using `sudo -E`, these host variables are automatically passed: `GITHUB_TOK
2525

2626
The following are always set/overridden: `PATH` (container values).
2727

28+
### Self-hosted runner home directory support
29+
30+
AWF derives the effective home directory at runtime from the host environment (`$HOME`, with sudo-aware handling), not from a hardcoded `/home/runner` path.
31+
32+
This means self-hosted Linux runners with non-standard service-account homes are supported, as long as `$HOME` is set correctly before invoking `awf`.
33+
2834
Variables from `--env` flags override everything else.
2935

3036
**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.

src/docker-manager.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,37 @@ describe('docker-manager', () => {
878878
expect(volumes).toContain(`/tmp/awf-test/agent-logs:/host${homeDir}/.copilot/logs:rw`);
879879
});
880880

881+
it('should create missing .copilot directory and mount it when using non-standard HOME path', () => {
882+
const fakeHome = fs.mkdtempSync(path.join(os.tmpdir(), 'awf-home-'));
883+
const originalHome = process.env.HOME;
884+
const originalSudoUser = process.env.SUDO_USER;
885+
delete process.env.SUDO_USER;
886+
process.env.HOME = fakeHome;
887+
888+
try {
889+
const copilotDir = path.join(fakeHome, '.copilot');
890+
expect(fs.existsSync(copilotDir)).toBe(false);
891+
892+
const result = generateDockerCompose(mockConfig, mockNetworkConfig);
893+
const volumes = result.services.agent.volumes as string[];
894+
895+
expect(fs.existsSync(copilotDir)).toBe(true);
896+
expect(volumes).toContain(`${fakeHome}/.copilot:/host${fakeHome}/.copilot:rw`);
897+
} finally {
898+
if (originalHome !== undefined) {
899+
process.env.HOME = originalHome;
900+
} else {
901+
delete process.env.HOME;
902+
}
903+
if (originalSudoUser !== undefined) {
904+
process.env.SUDO_USER = originalSudoUser;
905+
} else {
906+
delete process.env.SUDO_USER;
907+
}
908+
fs.rmSync(fakeHome, { recursive: true, force: true });
909+
}
910+
});
911+
881912
it('should use sessionStateDir when specified for chroot mounts', () => {
882913
const configWithSessionDir = { ...mockConfig, sessionStateDir: '/custom/session-state' };
883914
const result = generateDockerCompose(configWithSessionDir, mockNetworkConfig);

src/docker-manager.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1164,7 +1164,17 @@ export function generateDockerCompose(
11641164
// Mount ~/.copilot for Copilot CLI (package extraction, MCP config, etc.)
11651165
// This is safe as ~/.copilot contains only Copilot CLI state, not credentials.
11661166
// Auth tokens are in COPILOT_GITHUB_TOKEN env var (handled by API proxy sidecar).
1167-
agentVolumes.push(`${effectiveHome}/.copilot:/host${effectiveHome}/.copilot:rw`);
1167+
const copilotHomeDir = path.join(effectiveHome, '.copilot');
1168+
if (fs.existsSync(copilotHomeDir)) {
1169+
try {
1170+
fs.accessSync(copilotHomeDir, fs.constants.R_OK | fs.constants.W_OK);
1171+
agentVolumes.push(`${copilotHomeDir}:/host${effectiveHome}/.copilot:rw`);
1172+
} catch (error) {
1173+
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)}`);
1174+
}
1175+
} else {
1176+
logger.debug(`~/.copilot directory does not exist at ${copilotHomeDir}; skipping optional host bind mount.`);
1177+
}
11681178

11691179
// Overlay session-state and logs from AWF workDir so events.jsonl and logs are
11701180
// captured in the workDir instead of written to the host's ~/.copilot.

0 commit comments

Comments
 (0)