Skip to content

Commit 7d72846

Browse files
lpcoxCopilot
andcommitted
fix: keep ~/.copilot host mount with session-state/logs overlays
Removing the blanket ~/.copilot mount broke MCP config access and package extraction. Copilot CLI needs: - ~/.copilot/mcp-config.json (MCP server config, written by gh-aw framework) - ~/.copilot/pkg/ (package extraction during startup) Restore the host ~/.copilot bind mount and overlay session-state and logs from the AWF workDir on top. Docker processes mounts in order, so the later session-state and logs mounts shadow the corresponding paths under the parent ~/.copilot mount. Result: - MCP config and packages accessible from host (as before) - session-state → AWF workDir (events.jsonl captured) - logs → AWF workDir (agent logs captured) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent e296489 commit 7d72846

File tree

2 files changed

+12
-24
lines changed

2 files changed

+12
-24
lines changed

src/docker-manager.test.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -784,9 +784,8 @@ describe('docker-manager', () => {
784784
// CLI state directories
785785
expect(volumes).toContain(`${homeDir}/.claude:/host${homeDir}/.claude:rw`);
786786
expect(volumes).toContain(`${homeDir}/.anthropic:/host${homeDir}/.anthropic:rw`);
787-
// ~/.copilot is NOT blanket-mounted (security: may contain config/auth state)
788-
// Instead, session-state and logs are mounted from AWF workDir at chroot paths
789-
expect(volumes).not.toContain(`${homeDir}/.copilot:/host${homeDir}/.copilot:rw`);
787+
// ~/.copilot is mounted from host, with session-state and logs overlaid from AWF workDir
788+
expect(volumes).toContain(`${homeDir}/.copilot:/host${homeDir}/.copilot:rw`);
790789
expect(volumes).toContain(`/tmp/awf-test/agent-session-state:/host${homeDir}/.copilot/session-state:rw`);
791790
expect(volumes).toContain(`/tmp/awf-test/agent-logs:/host${homeDir}/.copilot/logs:rw`);
792791
});

src/docker-manager.ts

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -839,12 +839,15 @@ export function generateDockerCompose(
839839
// - One-shot token LD_PRELOAD library: /host/tmp/awf-lib/one-shot-token.so
840840
agentVolumes.push('/tmp:/host/tmp:rw');
841841

842-
// Mount session-state and logs at chroot paths so events.jsonl and logs are
843-
// captured (not written to the host's ~/.copilot via a blanket bind mount).
844-
// We intentionally do NOT mount the entire ~/.copilot directory, because it may
845-
// contain configuration or cached auth state that should not be exposed to the
846-
// sandboxed agent. The empty home volume (above) provides a writable ~/.copilot
847-
// for Copilot CLI to create any other files it needs.
842+
// Mount ~/.copilot for Copilot CLI (package extraction, MCP config, etc.)
843+
// This is safe as ~/.copilot contains only Copilot CLI state, not credentials.
844+
// Auth tokens are in COPILOT_GITHUB_TOKEN env var (handled by API proxy sidecar).
845+
agentVolumes.push(`${effectiveHome}/.copilot:/host${effectiveHome}/.copilot:rw`);
846+
847+
// Overlay session-state and logs from AWF workDir so events.jsonl and logs are
848+
// captured in the workDir instead of written to the host's ~/.copilot.
849+
// Docker processes mounts in order — these shadow the corresponding paths under
850+
// the blanket ~/.copilot mount above.
848851
agentVolumes.push(`${sessionStatePath}:/host${effectiveHome}/.copilot/session-state:rw`);
849852
agentVolumes.push(`${agentLogsPath}:/host${effectiveHome}/.copilot/logs:rw`);
850853

@@ -1029,7 +1032,7 @@ export function generateDockerCompose(
10291032
//
10301033
// Instead of mounting the entire $HOME directory (which contained credentials), we now:
10311034
// 1. Mount ONLY the workspace directory ($GITHUB_WORKSPACE or cwd)
1032-
// 2. Mount ~/.copilot/logs and ~/.copilot/session-state from AWF workDir (not host)
1035+
// 2. Mount ~/.copilot with session-state and logs overlaid from AWF workDir
10331036
// 3. Hide credential files by mounting /dev/null over them (defense-in-depth)
10341037
// 4. Allow users to add specific mounts via --mount flag
10351038
//
@@ -1682,20 +1685,6 @@ export async function writeConfigs(config: WrapperConfig): Promise<void> {
16821685
fs.chownSync(emptyHomeDir, uid, gid);
16831686
logger.debug(`Created chroot home directory: ${emptyHomeDir} (${uid}:${gid})`);
16841687

1685-
// Pre-create subdirectories inside the empty home volume so they exist with
1686-
// correct ownership after chroot. Without this, Docker auto-creates them as
1687-
// root-owned when mounting child volumes (e.g., .copilot/session-state),
1688-
// preventing user-level writes to sibling paths (e.g., .copilot/pkg).
1689-
const emptyHomeDirs = ['.copilot'];
1690-
for (const dir of emptyHomeDirs) {
1691-
const dirPath = path.join(emptyHomeDir, dir);
1692-
if (!fs.existsSync(dirPath)) {
1693-
fs.mkdirSync(dirPath, { recursive: true });
1694-
fs.chownSync(dirPath, uid, gid);
1695-
logger.debug(`Created chroot home subdirectory: ${dirPath} (${uid}:${gid})`);
1696-
}
1697-
}
1698-
16991688
// Ensure source directories for subdirectory mounts exist with correct ownership
17001689
const chrootHomeDirs = [
17011690
'.copilot', '.cache', '.config', '.local',

0 commit comments

Comments
 (0)