diff --git a/extensions/lobster/src/lobster-tool.ts b/extensions/lobster/src/lobster-tool.ts index 71dade859..60c0a2429 100644 --- a/extensions/lobster/src/lobster-tool.ts +++ b/extensions/lobster/src/lobster-tool.ts @@ -29,13 +29,22 @@ function resolveExecutablePath(lobsterPathRaw: string | undefined) { return lobsterPath; } -async function runLobsterSubprocess(params: { - execPath: string; - argv: string[]; - cwd: string; - timeoutMs: number; - maxStdoutBytes: number; -}) { +function isWindowsSpawnEINVAL(err: unknown) { + if (!err || typeof err !== "object") return false; + const code = (err as { code?: unknown }).code; + return code === "EINVAL"; +} + +async function runLobsterSubprocessOnce( + params: { + execPath: string; + argv: string[]; + cwd: string; + timeoutMs: number; + maxStdoutBytes: number; + }, + useShell: boolean, +) { const { execPath, argv, cwd } = params; const timeoutMs = Math.max(200, params.timeoutMs); const maxStdoutBytes = Math.max(1024, params.maxStdoutBytes); @@ -51,6 +60,8 @@ async function runLobsterSubprocess(params: { cwd, stdio: ["ignore", "pipe", "pipe"], env, + shell: useShell, + windowsHide: useShell ? true : undefined, }); let stdout = ""; @@ -102,6 +113,23 @@ async function runLobsterSubprocess(params: { }); } +async function runLobsterSubprocess(params: { + execPath: string; + argv: string[]; + cwd: string; + timeoutMs: number; + maxStdoutBytes: number; +}) { + try { + return await runLobsterSubprocessOnce(params, false); + } catch (err) { + if (process.platform === "win32" && isWindowsSpawnEINVAL(err)) { + return await runLobsterSubprocessOnce(params, true); + } + throw err; + } +} + function parseEnvelope(stdout: string): LobsterEnvelope { let parsed: unknown; try { diff --git a/src/infra/exec-approvals.test.ts b/src/infra/exec-approvals.test.ts index 0bb12c192..f6d77b2f1 100644 --- a/src/infra/exec-approvals.test.ts +++ b/src/infra/exec-approvals.test.ts @@ -253,10 +253,7 @@ describe("exec approvals default agent migration", () => { }; const resolved = resolveExecApprovalsFromFile({ file }); expect(resolved.agent.ask).toBe("always"); - expect(resolved.allowlist.map((entry) => entry.pattern)).toEqual([ - "/bin/main", - "/bin/legacy", - ]); + expect(resolved.allowlist.map((entry) => entry.pattern)).toEqual(["/bin/main", "/bin/legacy"]); expect(resolved.file.agents?.default).toBeUndefined(); }); });