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 {