From 14baad2cb0cdd2be60cf98726dfe88e826548431 Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Tue, 30 Jun 2026 14:57:13 -0700 Subject: [PATCH 1/3] fix: skip chroot hosts mount when sysroot-stage is active The generated /etc/hosts file (chroot-*/hosts) is written to the runner's /tmp which the Docker daemon cannot see on split-fs ARC/DinD. Skip this mount when sysroot is active since the volume already provides /etc/hosts. DNS pre-resolution is traded off; domains resolve at runtime. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/services/agent-volumes/volume-builder.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/services/agent-volumes/volume-builder.ts b/src/services/agent-volumes/volume-builder.ts index e74bea9be..03b7409f6 100644 --- a/src/services/agent-volumes/volume-builder.ts +++ b/src/services/agent-volumes/volume-builder.ts @@ -42,7 +42,13 @@ export function buildAgentVolumes(params: AgentVolumesParams): string[] { agentVolumes.push(...buildSystemMounts(workspaceDir, config.chrootBinariesSourcePath, useSysroot)); agentVolumes.push(...buildHomeMounts({ config, effectiveHome, agentLogsPath, sessionStatePath })); agentVolumes.push(...buildEtcMounts(config)); - agentVolumes.push(generateHostsFileMount(config)); + // When sysroot-stage is active, the sysroot volume provides /etc/hosts. + // The generated chroot hosts file lives on the runner's /tmp which the + // Docker daemon cannot see on split-fs. DNS pre-resolution is skipped; + // the agent resolves domains at runtime via the container's DNS config. + if (!useSysroot) { + agentVolumes.push(generateHostsFileMount(config)); + } agentVolumes.push(...buildDockerSocketMount(config)); agentVolumes.push(...buildSslMounts(sslConfig)); agentVolumes.push(...buildCustomVolumeMounts(config.volumeMounts)); From d11fe5fae7255b1f32e5746e0cd5bc24269074bf Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Tue, 30 Jun 2026 15:03:54 -0700 Subject: [PATCH 2/3] fix: filter out split-fs-invisible mounts when sysroot is active On ARC/DinD with split filesystem, the Docker daemon cannot see: - Paths under AWF's workDir (/tmp/awf-*) on the runner's /tmp - Runner home directory paths mounted to /host/home/... These bind mounts fail with OCI runtime errors. When sysroot-stage is active, drop them at compose generation time: - workDir-based mounts (initSignalDir, logs, session state, chroot-home) - Home directory mounts targeting /host (sysroot volume provides these) The agent still gets: - /tmp:/tmp:rw (daemon's own /tmp) - Workspace (shared between runner and daemon on ARC) - Kernel VFS (/sys, /dev) - Credential-hiding /dev/null overlays - The sysroot named volume at /host with full glibc filesystem Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/compose-generator.test.ts | 26 ++++++++++++++++++++++++++ src/compose-generator.ts | 27 +++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/compose-generator.test.ts b/src/compose-generator.test.ts index ebfbae07f..da710d1c4 100644 --- a/src/compose-generator.test.ts +++ b/src/compose-generator.test.ts @@ -495,5 +495,31 @@ describe('generateDockerCompose', () => { expect(result.volumes).toEqual({ sysroot: {} }); }); + + it('filters out workDir-based and home-based bind mounts on split-fs', () => { + const config = { + ...mockConfig, + runnerTopology: 'arc-dind' as const, + workDir: '/tmp/awf-12345', + }; + const result = generateDockerCompose(config, mockNetworkConfig); + const volumes = result.services.agent.volumes as string[]; + + // workDir-based mounts should be dropped + expect(volumes.some(v => v.startsWith('/tmp/awf-12345'))).toBe(false); + + // Home-based mounts targeting /host/home should be dropped + const homeTargets = volumes.filter(v => { + const target = v.split(':')[1]; + return target.startsWith('/host/home') && !v.startsWith('/dev/null'); + }); + expect(homeTargets).toHaveLength(0); + + // Should still have /tmp:/tmp, /sys, /dev, sysroot volume + expect(volumes).toContain('/tmp:/tmp:rw'); + expect(volumes).toContain('/sys:/host/sys:ro'); + expect(volumes).toContain('/dev:/host/dev:ro'); + expect(volumes).toContain('sysroot:/host:rw'); + }); }); }); diff --git a/src/compose-generator.ts b/src/compose-generator.ts index 41294d99c..6fb7f2117 100644 --- a/src/compose-generator.ts +++ b/src/compose-generator.ts @@ -126,9 +126,32 @@ export function generateDockerCompose( '/host/lib64', '/host/opt', ]); + + // On split-fs ARC/DinD, the Docker daemon cannot see the runner's + // filesystem paths. Filter out bind mounts the daemon can't resolve: + // - Source under workDir (runner's unshared /tmp/awf-*): daemon can't see it + // - Source under effectiveHome with target under /host: sysroot volume provides these + // - Sysroot-shadowed targets: system binaries already in the sysroot volume + // Keep: /tmp:/tmp (daemon has its own), /dev/null overlays, /dev and /sys + // (kernel VFS), workspace mounts (ARC shares workspace with daemon). + const workDirPrefix = config.workDir; + const hostHomeMountPrefix = `/host${effectiveHome}`; + const filteredVolumes = agentVolumes.filter(volume => { - const target = volume.split(':')[1]; - return !sysrootShadowedTargets.has(target); + const parts = volume.split(':'); + const source = parts[0]; + const target = parts[1]; + + // Drop sysroot-shadowed targets (system binaries provided by volume) + if (sysrootShadowedTargets.has(target)) return false; + + // Drop mounts sourced from AWF workDir (runner's unshared /tmp/awf-*) + if (source.startsWith(workDirPrefix)) return false; + + // Drop home directory mounts targeting /host/home/... — sysroot provides them + if (source.startsWith(effectiveHome) && target.startsWith(hostHomeMountPrefix)) return false; + + return true; }); agentVolumes.length = 0; agentVolumes.push(...filteredVolumes); From bfb9b9ad3b53932c5b15f9203bb4385b72001941 Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Tue, 30 Jun 2026 15:14:55 -0700 Subject: [PATCH 3/3] fix: guard against malformed volume strings in sysroot filter Add parts.length < 2 check to avoid undefined target when a volume mount string lacks the expected ':' separator. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/compose-generator.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/compose-generator.ts b/src/compose-generator.ts index 6fb7f2117..4d0b89a3a 100644 --- a/src/compose-generator.ts +++ b/src/compose-generator.ts @@ -139,6 +139,7 @@ export function generateDockerCompose( const filteredVolumes = agentVolumes.filter(volume => { const parts = volume.split(':'); + if (parts.length < 2) return true; // Keep malformed entries unchanged const source = parts[0]; const target = parts[1];