diff --git a/CHANGELOG.md b/CHANGELOG.md index 7295e24d339..1d01248f7b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,6 +80,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- UI/Windows: quote resolved pnpm `.cmd` launcher paths before spawning UI install/build/test commands so Node installs under `C:\Program Files` no longer fail as `C:\Program`. Fixes #45275. Thanks @Kobevictor, @stoppieboy, and @iubns. - Plugins/uninstall: remove tracked plugin files from their recorded managed extensions root even when the current state directory points somewhere else, so `openclaw plugins uninstall --force` does not leave the plugin discoverable. Thanks @shakkernerd. - Agents/runtime: add `agentRuntime.id` as the canonical config key, migrate legacy runtime-policy configs with `openclaw doctor --fix`, and route diff --git a/scripts/ui.js b/scripts/ui.js index 8f7b8e0cf02..0b5f4be436e 100644 --- a/scripts/ui.js +++ b/scripts/ui.js @@ -76,6 +76,10 @@ export function assertSafeWindowsShellArgs(args, platform = process.platform) { ); } +export function prepareSpawnCommand(cmd, platform = process.platform) { + return shouldUseShellForCommand(cmd, platform) ? `"${cmd}"` : cmd; +} + function createSpawnOptions(cmd, args, envOverride) { const useShell = shouldUseShellForCommand(cmd); if (useShell) { @@ -92,7 +96,7 @@ function createSpawnOptions(cmd, args, envOverride) { function run(cmd, args) { let child; try { - child = spawn(cmd, args, createSpawnOptions(cmd, args)); + child = spawn(prepareSpawnCommand(cmd), args, createSpawnOptions(cmd, args)); } catch (err) { console.error(`Failed to launch ${cmd}:`, err); process.exit(1); @@ -113,7 +117,7 @@ function run(cmd, args) { function runSync(cmd, args, envOverride) { let result; try { - result = spawnSync(cmd, args, createSpawnOptions(cmd, args, envOverride)); + result = spawnSync(prepareSpawnCommand(cmd), args, createSpawnOptions(cmd, args, envOverride)); } catch (err) { console.error(`Failed to launch ${cmd}:`, err); process.exit(1); diff --git a/test/scripts/ui.test.ts b/test/scripts/ui.test.ts index 170d964f369..86c1ca66243 100644 --- a/test/scripts/ui.test.ts +++ b/test/scripts/ui.test.ts @@ -1,5 +1,9 @@ import { describe, expect, it } from "vitest"; -import { assertSafeWindowsShellArgs, shouldUseShellForCommand } from "../../scripts/ui.js"; +import { + assertSafeWindowsShellArgs, + prepareSpawnCommand, + shouldUseShellForCommand, +} from "../../scripts/ui.js"; describe("scripts/ui windows spawn behavior", () => { it("enables shell for Windows command launchers that require cmd.exe", () => { @@ -14,6 +18,16 @@ describe("scripts/ui windows spawn behavior", () => { expect(shouldUseShellForCommand("/usr/local/bin/pnpm", "linux")).toBe(false); }); + it("quotes Windows shell launcher paths before passing them to spawn", () => { + expect(prepareSpawnCommand("C:\\Program Files\\nodejs\\pnpm.cmd", "win32")).toBe( + '"C:\\Program Files\\nodejs\\pnpm.cmd"', + ); + expect(prepareSpawnCommand("C:\\Program Files\\nodejs\\pnpm.exe", "win32")).toBe( + "C:\\Program Files\\nodejs\\pnpm.exe", + ); + expect(prepareSpawnCommand("/usr/local/bin/pnpm", "linux")).toBe("/usr/local/bin/pnpm"); + }); + it("allows safe forwarded args when shell mode is required on Windows", () => { expect(() => assertSafeWindowsShellArgs(["run", "build", "--filter", "@openclaw/ui"], "win32"),