diff --git a/packages/cli/src/commands/daemon/status.ts b/packages/cli/src/commands/daemon/status.ts index 470447833..132fe4876 100644 --- a/packages/cli/src/commands/daemon/status.ts +++ b/packages/cli/src/commands/daemon/status.ts @@ -175,6 +175,7 @@ function checkProviderBinary(binary: string): { path: string | null; version: st timeout: 5000, stdio: ["ignore", "pipe", "pipe"], env, + shell: process.platform === "win32", }).trim(); return { path: binaryPath, version: output || null }; } catch { diff --git a/packages/server/src/server/agent/provider-launch-config.test.ts b/packages/server/src/server/agent/provider-launch-config.test.ts index 85e12ff8a..6ce4ca983 100644 --- a/packages/server/src/server/agent/provider-launch-config.test.ts +++ b/packages/server/src/server/agent/provider-launch-config.test.ts @@ -168,6 +168,24 @@ describe("findExecutable", () => { 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", () => { findExecutableDependencies.shell = "/bin/zsh"; findExecutableDependencies.execSync.mockReturnValue( diff --git a/packages/server/src/server/agent/provider-launch-config.ts b/packages/server/src/server/agent/provider-launch-config.ts index d03dba743..326f404cf 100644 --- a/packages/server/src/server/agent/provider-launch-config.ts +++ b/packages/server/src/server/agent/provider-launch-config.ts @@ -187,7 +187,10 @@ export function applyProviderEnv( * 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`. * - * 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( name: string, @@ -228,8 +231,12 @@ export function findExecutable( Path: resolvedPath, }; const out = deps.execFileSync("where.exe", [trimmed], { encoding: "utf8", env }).trim(); - const firstLine = out.split(/\r?\n/)[0]?.trim(); - return firstLine || null; + return ( + out + .split(/\r?\n/) + .map((line) => line.trim()) + .find((line) => line.length > 0) ?? null + ); } catch { return null; } diff --git a/packages/server/src/server/agent/providers/claude-agent.ts b/packages/server/src/server/agent/providers/claude-agent.ts index 9e2dd9237..3cfb20299 100644 --- a/packages/server/src/server/agent/providers/claude-agent.ts +++ b/packages/server/src/server/agent/providers/claude-agent.ts @@ -219,6 +219,7 @@ function applyRuntimeSettingsToClaudeOptions( ...applyProviderEnv(spawnOptions.env, runtimeSettings), ...(launchEnv ?? {}), }, + shell: process.platform === "win32", signal: spawnOptions.signal, stdio: ["pipe", "pipe", "pipe"], }); diff --git a/packages/server/src/server/agent/providers/codex-app-server-agent.ts b/packages/server/src/server/agent/providers/codex-app-server-agent.ts index 3c733c79d..ccd83cf8c 100644 --- a/packages/server/src/server/agent/providers/codex-app-server-agent.ts +++ b/packages/server/src/server/agent/providers/codex-app-server-agent.ts @@ -3421,6 +3421,7 @@ export class CodexAppServerAgentClient implements AgentClient { ); return spawn(launchPrefix.command, [...launchPrefix.args, "app-server"], { detached: process.platform !== "win32", + shell: process.platform === "win32", stdio: ["pipe", "pipe", "pipe"], env: buildCodexAppServerEnv(this.runtimeSettings, launchEnv), }); diff --git a/packages/server/src/server/agent/providers/opencode-agent.ts b/packages/server/src/server/agent/providers/opencode-agent.ts index f8a087f5b..a129675d4 100644 --- a/packages/server/src/server/agent/providers/opencode-agent.ts +++ b/packages/server/src/server/agent/providers/opencode-agent.ts @@ -336,6 +336,7 @@ export class OpenCodeServerManager { launchPrefix.command, [...launchPrefix.args, "serve", "--port", String(this.port)], { + shell: process.platform === "win32", stdio: ["ignore", "pipe", "pipe"], env: applyProviderEnv(process.env, this.runtimeSettings), },