From 7973aa6f41f06091fc9316afd743c26489b6da85 Mon Sep 17 00:00:00 2001 From: Flow-Fly <44248262+Flow-Fly@users.noreply.github.com> Date: Thu, 30 Oct 2025 10:54:38 +0000 Subject: [PATCH] =?UTF-8?q?fix(windows):=20support=20spaces=20in=20Node=20?= =?UTF-8?q?path=20(remove=20shell,=20normalize=20paths);=20trim=20surround?= =?UTF-8?q?ing=20quotes=20in=20user-provided=20path\n\n-=20Spawn=20Node=20?= =?UTF-8?q?without=20a=20shell=20so=20paths=20with=20spaces=20aren?= =?UTF-8?q?=E2=80=99t=20split\n-=20Normalize=20provided=20Node=20path=20an?= =?UTF-8?q?d=20agent=20path\n-=20Strip=20surrounding=20quotes=20from=20use?= =?UTF-8?q?r-entered=20path=20to=20avoid=20fs/spawn=20issues\n\nFixes=20#9?= =?UTF-8?q?2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/copilot/CopilotAgent.ts | 7 ++++--- src/helpers/Node.ts | 18 +++++++++++++----- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/copilot/CopilotAgent.ts b/src/copilot/CopilotAgent.ts index 4bd00b7..8abb6b1 100644 --- a/src/copilot/CopilotAgent.ts +++ b/src/copilot/CopilotAgent.ts @@ -11,6 +11,7 @@ import Logger from "../helpers/Logger"; import Json from "../helpers/Json"; import Client, { CopilotResponse } from "./Client"; import File from "../helpers/File"; +import Node from "../helpers/Node"; import { GetCompletionsParams } from "@pierrad/ts-lsp-client"; import { InlineSuggestionEffect } from "../extensions/InlineSuggestionState"; @@ -51,10 +52,10 @@ class CopilotAgent implements SettingsObserver { public startAgent(): void { try { this.agent = spawn( - File.wrapFilePath(this.plugin.settings.nodePath), - [File.wrapFilePath(this.agentPath), "--stdio"], + // Spawn the executable directly to avoid shell parsing issues with spaces + Node.normalizePath(this.plugin.settings.nodePath), + [Node.normalizePath(this.agentPath), "--stdio"], { - shell: true, stdio: "pipe", ...(this.plugin.settings.proxy && { env: { diff --git a/src/helpers/Node.ts b/src/helpers/Node.ts index d4afb45..b577e01 100644 --- a/src/helpers/Node.ts +++ b/src/helpers/Node.ts @@ -10,6 +10,14 @@ class Node { return nodePath; } + // Trim surrounding whitespace and remove surrounding quotes if the user + // pasted a quoted path (common on Windows when copying paths). + nodePath = nodePath.trim(); + + if ((nodePath.startsWith('"') && nodePath.endsWith('"')) || (nodePath.startsWith("'") && nodePath.endsWith("'"))) { + nodePath = nodePath.slice(1, -1); + } + if (nodePath.startsWith("~")) { nodePath = nodePath.replace(/^~(?=$|\/|\\)/, os.homedir()); } @@ -74,11 +82,11 @@ class Node { } const result = await new Promise((resolve, reject) => { - let spawnOptions = {}; - - if (os.platform() === "win32") { - spawnOptions = { shell: true }; - } + // Do not run under a shell. Using a shell on Windows will split paths + // that contain spaces (eg. "C:\Program Files\nodejs\node.exe") and + // cause the command to fail. Spawn the executable directly so paths + // with spaces are handled correctly. + const spawnOptions = {}; const nodeProcess = child_process.spawn( normalizedPath,