From dacffd7ac8762415298c3ee07a6e7e2d27b710cd Mon Sep 17 00:00:00 2001 From: Rain Date: Mon, 16 Feb 2026 18:21:56 +0800 Subject: [PATCH] fix(sandbox): parse Windows bind mounts in fs-path mapping --- src/agents/sandbox/fs-paths.test.ts | 13 ++++++++ src/agents/sandbox/fs-paths.ts | 49 ++++++++++++++++++++++++++--- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/src/agents/sandbox/fs-paths.test.ts b/src/agents/sandbox/fs-paths.test.ts index afc3fe75ec..c7848e89c6 100644 --- a/src/agents/sandbox/fs-paths.test.ts +++ b/src/agents/sandbox/fs-paths.test.ts @@ -25,6 +25,19 @@ describe("parseSandboxBindMount", () => { writable: true, }); }); + + it("parses Windows drive-letter host paths", () => { + expect(parseSandboxBindMount("C:\\Users\\kai\\workspace:/workspace:ro")).toEqual({ + hostRoot: path.resolve("C:\\Users\\kai\\workspace"), + containerRoot: "/workspace", + writable: false, + }); + expect(parseSandboxBindMount("D:/data:/workspace-data:rw")).toEqual({ + hostRoot: path.resolve("D:/data"), + containerRoot: "/workspace-data", + writable: true, + }); + }); }); describe("resolveSandboxFsPathWithMounts", () => { diff --git a/src/agents/sandbox/fs-paths.ts b/src/agents/sandbox/fs-paths.ts index 6b09682b1d..8302234e08 100644 --- a/src/agents/sandbox/fs-paths.ts +++ b/src/agents/sandbox/fs-paths.ts @@ -23,21 +23,29 @@ type ParsedBindMount = { writable: boolean; }; +type SplitBindSpec = { + host: string; + container: string; + options: string; +}; + export function parseSandboxBindMount(spec: string): ParsedBindMount | null { const trimmed = spec.trim(); if (!trimmed) { return null; } - const parts = trimmed.split(":"); - if (parts.length < 2) { + + const parsed = splitBindSpec(trimmed); + if (!parsed) { return null; } - const hostToken = (parts[0] ?? "").trim(); - const containerToken = (parts[1] ?? "").trim(); + + const hostToken = parsed.host.trim(); + const containerToken = parsed.container.trim(); if (!hostToken || !containerToken || !path.posix.isAbsolute(containerToken)) { return null; } - const optionsToken = parts.slice(2).join(":").trim().toLowerCase(); + const optionsToken = parsed.options.trim().toLowerCase(); const optionParts = optionsToken ? optionsToken .split(",") @@ -52,6 +60,37 @@ export function parseSandboxBindMount(spec: string): ParsedBindMount | null { }; } +function splitBindSpec(spec: string): SplitBindSpec | null { + // Windows drive-letter host path: C:\\path:/container[:opts] or C:/path:/container[:opts] + if (/^[A-Za-z]:[\\/]/.test(spec)) { + const hostEnd = spec.indexOf(":", 2); + if (hostEnd === -1) { + return null; + } + const host = spec.slice(0, hostEnd); + const rest = spec.slice(hostEnd + 1); + const optionsStart = rest.indexOf(":"); + if (optionsStart === -1) { + return { host, container: rest, options: "" }; + } + return { + host, + container: rest.slice(0, optionsStart), + options: rest.slice(optionsStart + 1), + }; + } + + const parts = spec.split(":"); + if (parts.length < 2) { + return null; + } + return { + host: parts[0] ?? "", + container: parts[1] ?? "", + options: parts.slice(2).join(":"), + }; +} + export function buildSandboxFsMounts(sandbox: SandboxContext): SandboxFsMount[] { const mounts: SandboxFsMount[] = [ {