From d6751a1cdd666783ff9b0fb9f8e1d7f7aa1d28fa Mon Sep 17 00:00:00 2001 From: Contributor Date: Thu, 25 Jun 2026 18:19:59 +0800 Subject: [PATCH] fix(windows): resolve claude.cmd launcher and spawn with shell On Windows, `where claude` lists the extension-less npm sh shim first. child_process.spawn without a shell uses CreateProcess, which cannot execute that shim, so launching Claude Code fails with `ENOENT: no such file or directory, uv_spawn '...\npm\claude'`. - resolveClaudePath: prefer the .cmd/.exe/.bat candidate from `where` - runClaudeCode: spawn with shell:true on win32 so cmd.exe runs the resolved .cmd launcher Co-Authored-By: Claude --- src/cli.ts | 3 +++ src/path.ts | 13 ++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/cli.ts b/src/cli.ts index bb4a3a7..b43f11b 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -827,6 +827,9 @@ async function runClaudeCode( const child = spawn(claudePath, spawnArgs, { stdio: "inherit", env, + // On Windows the resolved launcher is a .cmd batch file; CreateProcess + // cannot run .cmd directly, it must go through cmd.exe (shell: true). + shell: process.platform === "win32", }); child.on("error", (err) => { diff --git a/src/path.ts b/src/path.ts index 76c1cf7..5222548 100644 --- a/src/path.ts +++ b/src/path.ts @@ -14,7 +14,18 @@ export function getInstallationPath(id: string = DEFAULT_INSTALLATION_ID): strin export function resolveClaudePath(): string { try { if (process.platform === "win32") { - return execSync("where claude", { encoding: "utf-8" }).trim().split(/\r?\n/)[0] ?? "claude"; + const candidates = execSync("where claude", { encoding: "utf-8" }) + .trim() + .split(/\r?\n/) + .filter(Boolean); + // `where` lists the extension-less sh shim first. CreateProcess (used by + // child_process.spawn without a shell) cannot execute it, so pick the + // .cmd/.exe/.bat launcher that Windows actually runs. + return ( + candidates.find((p) => /\.(cmd|exe|bat)$/i.test(p)) ?? + candidates[0] ?? + "claude" + ); } return execSync("which claude", { encoding: "utf-8" }).trim(); } catch {