From 4d0fe57a7ac86576d79c43e0cea0a11800cdc8a8 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Thu, 16 Apr 2026 18:36:06 +0700 Subject: [PATCH] fix(server): stop detecting Windows PowerShell shims --- packages/server/src/utils/executable.test.ts | 14 ++++++- packages/server/src/utils/executable.ts | 40 ++++--------------- .../src/utils/spawn.launch-regression.test.ts | 16 +------- packages/server/src/utils/spawn.ts | 28 +------------ 4 files changed, 23 insertions(+), 75 deletions(-) diff --git a/packages/server/src/utils/executable.test.ts b/packages/server/src/utils/executable.test.ts index b24dc2c1a..9a6d1e0fb 100644 --- a/packages/server/src/utils/executable.test.ts +++ b/packages/server/src/utils/executable.test.ts @@ -138,7 +138,7 @@ describe("executableExists", () => { expect(executableExists("/usr/local/bin/codex", exists)).toBe("/usr/local/bin/codex"); }); - test("on Windows, falls back to .exe, .cmd, then .ps1 for extensionless paths", () => { + test("on Windows, falls back to .exe, then .cmd for extensionless paths", () => { const originalPlatform = process.platform; Object.defineProperty(process, "platform", { value: "win32", writable: true }); try { @@ -150,6 +150,18 @@ describe("executableExists", () => { } }); + test("on Windows, ignores PowerShell scripts for extensionless paths", () => { + const originalPlatform = process.platform; + Object.defineProperty(process, "platform", { value: "win32", writable: true }); + try { + const exists = (candidate: string) => candidate === "C:\\tools\\codex.ps1"; + + expect(executableExists("C:\\tools\\codex", exists)).toBeNull(); + } finally { + Object.defineProperty(process, "platform", { value: originalPlatform, writable: true }); + } + }); + test("returns null when no matching path exists", () => { expect(executableExists("/missing/codex", () => false)).toBeNull(); }); diff --git a/packages/server/src/utils/executable.ts b/packages/server/src/utils/executable.ts index 84a53543f..f06786b3f 100644 --- a/packages/server/src/utils/executable.ts +++ b/packages/server/src/utils/executable.ts @@ -40,25 +40,6 @@ export function isWindowsCommandScript(executablePath: string): boolean { return process.platform === "win32" && (extension === ".cmd" || extension === ".bat"); } -export function isWindowsPowerShellScript(executablePath: string): boolean { - return process.platform === "win32" && extname(executablePath).toLowerCase() === ".ps1"; -} - -export function windowsPowerShellScriptArgs(scriptPath: string, args: string[]): string[] { - const argvBase64 = Buffer.from(JSON.stringify(args), "utf16le").toString("base64"); - const escapedScriptPath = scriptPath.replace(/'/g, "''"); - const command = [ - "$ErrorActionPreference = 'Stop'", - `$argvJson = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String('${argvBase64}'))`, - "$argv = @((ConvertFrom-Json -InputObject $argvJson))", - `& '${escapedScriptPath}' @argv`, - "exit $LASTEXITCODE", - ].join("; "); - const encodedCommand = Buffer.from(command, "utf16le").toString("base64"); - - return ["-NoProfile", "-ExecutionPolicy", "Bypass", "-EncodedCommand", encodedCommand]; -} - async function probeExecutable(executablePath: string): Promise { return await new Promise((resolve) => { let settled = false; @@ -78,19 +59,12 @@ async function probeExecutable(executablePath: string): Promise { let child: ChildProcess; try { - const isPowerShellScript = isWindowsPowerShellScript(executablePath); - child = spawn( - isPowerShellScript ? "powershell.exe" : executablePath, - isPowerShellScript - ? windowsPowerShellScriptArgs(executablePath, ["--version"]) - : ["--version"], - { - stdio: "ignore", - windowsHide: true, - // Windows batch shims (.cmd/.bat) require cmd.exe; native binaries do not. - shell: isWindowsCommandScript(executablePath), - }, - ); + child = spawn(executablePath, ["--version"], { + stdio: "ignore", + windowsHide: true, + // Windows batch shims (.cmd/.bat) require cmd.exe; native binaries do not. + shell: isWindowsCommandScript(executablePath), + }); } catch { settle(false); return; @@ -128,7 +102,7 @@ export function executableExists( ): string | null { if (exists(executablePath)) return executablePath; if (process.platform === "win32" && !extname(executablePath)) { - for (const ext of [".exe", ".cmd", ".ps1"]) { + for (const ext of [".exe", ".cmd"]) { const candidate = executablePath + ext; if (exists(candidate)) return candidate; } diff --git a/packages/server/src/utils/spawn.launch-regression.test.ts b/packages/server/src/utils/spawn.launch-regression.test.ts index 29df428ae..c1087962b 100644 --- a/packages/server/src/utils/spawn.launch-regression.test.ts +++ b/packages/server/src/utils/spawn.launch-regression.test.ts @@ -216,24 +216,12 @@ describe.runIf(process.platform === "win32")("Windows spawn launch regression", expect(result.stdout.trim()).toBe("ARGV_OK"); }); - test("detects and launches a PowerShell shim from PATH without corrupting JSON args", async () => { + test("does not detect a PowerShell shim from PATH", async () => { const fixture = makeFixture({ includeCmdShim: false }); await withWindowsPathEntry(fixture.root, async () => { const detected = await findExecutable("claude"); - expect(detected?.toLowerCase()).toBe(fixture.powerShellShim.toLowerCase()); - - const result = await runFixture({ - command: detected!, - args: fixture.expectedArgs, - shell: false, - }); - - expect(result.error).toBeNull(); - expect(result.code).toBe(0); - expect(result.signal).toBeNull(); - expect(result.stderr).toBe(""); - expect(result.stdout.trim()).toBe("ARGV_OK"); + expect(detected).toBeNull(); }); }); }); diff --git a/packages/server/src/utils/spawn.ts b/packages/server/src/utils/spawn.ts index b5de30366..f7a0b0881 100644 --- a/packages/server/src/utils/spawn.ts +++ b/packages/server/src/utils/spawn.ts @@ -1,13 +1,7 @@ import { execFile, spawn, type ChildProcess, type SpawnOptions } from "node:child_process"; import { promisify } from "node:util"; -import { - isWindowsCommandScript, - isWindowsPowerShellScript, - quoteWindowsArgument, - quoteWindowsCommand, - windowsPowerShellScriptArgs, -} from "./executable.js"; +import { isWindowsCommandScript, quoteWindowsArgument, quoteWindowsCommand } from "./executable.js"; const execFileAsync = promisify(execFile); @@ -30,14 +24,6 @@ export function spawnProcess( options?: SpawnOptions, ): ChildProcess { const isWindows = process.platform === "win32"; - if (isWindowsPowerShellScript(command)) { - return spawn("powershell.exe", windowsPowerShellScriptArgs(command, args), { - ...options, - shell: false, - windowsHide: true, - }); - } - const shell = isWindowsCommandScript(command) ? true : (options?.shell ?? isWindows); const shouldQuoteForShell = isWindows && shell !== false; @@ -57,18 +43,6 @@ export async function execCommand( options?: ExecCommandOptions, ): Promise { const isWindows = process.platform === "win32"; - if (isWindowsPowerShellScript(command)) { - return execFileAsync("powershell.exe", windowsPowerShellScriptArgs(command, args), { - cwd: options?.cwd, - env: options?.env, - encoding: options?.encoding ?? "utf8", - timeout: options?.timeout, - maxBuffer: options?.maxBuffer, - shell: false, - windowsHide: true, - }) as Promise; - } - const shell = isWindowsCommandScript(command) ? true : isWindows; const shouldQuoteForShell = isWindows && shell !== false; const resolvedCommand = shouldQuoteForShell ? quoteWindowsCommand(command) : command;