fix(server): use shell on Windows for all provider spawns

Windows npm shims (e.g. C:\nvm4w\nodejs\codex) fail with ENOENT when
spawned without shell. Use `shell: true` on win32 for all provider
launches instead of the overly complex shouldUseWindowsShell function.
This commit is contained in:
Mohamed Boudra
2026-04-01 15:19:28 +07:00
parent 2d02db6ae0
commit cce8dee21c
6 changed files with 32 additions and 3 deletions

View File

@@ -175,6 +175,7 @@ function checkProviderBinary(binary: string): { path: string | null; version: st
timeout: 5000, timeout: 5000,
stdio: ["ignore", "pipe", "pipe"], stdio: ["ignore", "pipe", "pipe"],
env, env,
shell: process.platform === "win32",
}).trim(); }).trim();
return { path: binaryPath, version: output || null }; return { path: binaryPath, version: output || null };
} catch { } catch {

View File

@@ -168,6 +168,24 @@ describe("findExecutable", () => {
expect(env.Path).toContain("C:\\Users\\boudr\\.local\\bin"); expect(env.Path).toContain("C:\\Users\\boudr\\.local\\bin");
}); });
test("on Windows, preserves the first where.exe match", () => {
findExecutableDependencies.platform = vi.fn(() => "win32");
process.env.Path = "C:\\Windows\\System32";
findExecutableDependencies.execFileSync.mockImplementation(
((command: string) => {
if (command === "powershell") {
return "C:\\Windows\\System32\r\nC:\\nvm4w\\nodejs\r\n";
}
if (command === "where.exe") {
return "C:\\nvm4w\\nodejs\\codex\r\nC:\\nvm4w\\nodejs\\codex.cmd\r\n";
}
throw new Error(`unexpected command ${command}`);
}) as any,
);
expect(findExecutable("codex", findExecutableDependencies)).toBe("C:\\nvm4w\\nodejs\\codex");
});
test("uses the last line from login-shell which output", () => { test("uses the last line from login-shell which output", () => {
findExecutableDependencies.shell = "/bin/zsh"; findExecutableDependencies.shell = "/bin/zsh";
findExecutableDependencies.execSync.mockReturnValue( findExecutableDependencies.execSync.mockReturnValue(

View File

@@ -187,7 +187,10 @@ export function applyProviderEnv(
* user opened a terminal and typed the command. If that fails (e.g. the login * user opened a terminal and typed the command. If that fails (e.g. the login
* shell itself errors) we fall back to a plain `which`. * shell itself errors) we fall back to a plain `which`.
* *
* On Windows the system PATH is always available, so `where.exe` is sufficient. * On Windows we augment the daemon PATH with machine/user registry PATH values
* and return the first `where.exe` match. Launch-time execution decides whether
* the resolved path needs `cmd.exe` semantics (for example npm shims under
* nvm4w such as `C:\nvm4w\nodejs\codex`).
*/ */
export function findExecutable( export function findExecutable(
name: string, name: string,
@@ -228,8 +231,12 @@ export function findExecutable(
Path: resolvedPath, Path: resolvedPath,
}; };
const out = deps.execFileSync("where.exe", [trimmed], { encoding: "utf8", env }).trim(); const out = deps.execFileSync("where.exe", [trimmed], { encoding: "utf8", env }).trim();
const firstLine = out.split(/\r?\n/)[0]?.trim(); return (
return firstLine || null; out
.split(/\r?\n/)
.map((line) => line.trim())
.find((line) => line.length > 0) ?? null
);
} catch { } catch {
return null; return null;
} }

View File

@@ -219,6 +219,7 @@ function applyRuntimeSettingsToClaudeOptions(
...applyProviderEnv(spawnOptions.env, runtimeSettings), ...applyProviderEnv(spawnOptions.env, runtimeSettings),
...(launchEnv ?? {}), ...(launchEnv ?? {}),
}, },
shell: process.platform === "win32",
signal: spawnOptions.signal, signal: spawnOptions.signal,
stdio: ["pipe", "pipe", "pipe"], stdio: ["pipe", "pipe", "pipe"],
}); });

View File

@@ -3421,6 +3421,7 @@ export class CodexAppServerAgentClient implements AgentClient {
); );
return spawn(launchPrefix.command, [...launchPrefix.args, "app-server"], { return spawn(launchPrefix.command, [...launchPrefix.args, "app-server"], {
detached: process.platform !== "win32", detached: process.platform !== "win32",
shell: process.platform === "win32",
stdio: ["pipe", "pipe", "pipe"], stdio: ["pipe", "pipe", "pipe"],
env: buildCodexAppServerEnv(this.runtimeSettings, launchEnv), env: buildCodexAppServerEnv(this.runtimeSettings, launchEnv),
}); });

View File

@@ -336,6 +336,7 @@ export class OpenCodeServerManager {
launchPrefix.command, launchPrefix.command,
[...launchPrefix.args, "serve", "--port", String(this.port)], [...launchPrefix.args, "serve", "--port", String(this.port)],
{ {
shell: process.platform === "win32",
stdio: ["ignore", "pipe", "pipe"], stdio: ["ignore", "pipe", "pipe"],
env: applyProviderEnv(process.env, this.runtimeSettings), env: applyProviderEnv(process.env, this.runtimeSettings),
}, },