From 602097477a2301b493f94da81a020e4cfc63a9d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Kondratek?= <19799111+mkondratek@users.noreply.github.com> Date: Thu, 9 Jul 2026 13:53:58 +0200 Subject: [PATCH] fix(terminal): read host IDE shell from CLINE_TERMINAL_SHELL_PATH instead of COMSPEC/SHELL Host integrations (the JetBrains plugin) forwarded the user's IDE terminal shell by overriding COMSPEC on Windows. COMSPEC is an OS-level contract that must keep pointing at cmd.exe: cross-spawn, Node's shell:true, and npm .cmd shims all pass cmd-style /d /s /c arguments to whatever it references. With IntelliJ's default terminal shell (PowerShell) forwarded as COMSPEC, every .cmd-shimmed child process spawned by cline-core broke - e.g. the Claude Code provider failed with: /d : The term '/d' is not recognized ... Introduce a dedicated CLINE_TERMINAL_SHELL_PATH env var that hosts can set safely. It takes precedence in getShell(), in the standalone terminal's default-shell pick, and in the system-prompt shell report for background exec mode, while COMSPEC/SHELL keep their OS semantics. Fixes #11290 --- .../system-prompt/components/system_info.ts | 10 ++++- .../standalone/StandaloneTerminalProcess.ts | 7 ++++ apps/vscode/src/test/shell.test.ts | 39 ++++++++++++++++++- apps/vscode/src/utils/shell.ts | 28 +++++++++++++ 4 files changed, 81 insertions(+), 3 deletions(-) diff --git a/apps/vscode/src/core/prompts/system-prompt/components/system_info.ts b/apps/vscode/src/core/prompts/system-prompt/components/system_info.ts index e39beec21c..1db6cb6b68 100644 --- a/apps/vscode/src/core/prompts/system-prompt/components/system_info.ts +++ b/apps/vscode/src/core/prompts/system-prompt/components/system_info.ts @@ -1,5 +1,5 @@ import osModule from "node:os" -import { getShell } from "@utils/shell" +import { getHostProvidedShell, getShell } from "@utils/shell" import osName from "os-name" import { getWorkspacePaths } from "@/hosts/vscode/hostbridge/workspace/getWorkspacePaths" import { SystemPromptSection } from "../templates/placeholders" @@ -21,7 +21,13 @@ Home Directory: {{homeDir}} */ function getEffectiveShell(context: SystemPromptContext): string { if (context.terminalExecutionMode === "backgroundExec") { - // Background exec uses the system default shell, not VS Code config + // Background exec uses the host-forwarded shell if present (mirrors + // StandaloneTerminalProcess.getDefaultShell), otherwise the system + // default shell, not the VS Code config. + const hostShell = getHostProvidedShell() + if (hostShell) { + return hostShell + } if (process.platform === "win32") { return process.env.COMSPEC || "cmd.exe" } else { diff --git a/apps/vscode/src/integrations/terminal/standalone/StandaloneTerminalProcess.ts b/apps/vscode/src/integrations/terminal/standalone/StandaloneTerminalProcess.ts index b7376ed7c0..789fff1cdf 100644 --- a/apps/vscode/src/integrations/terminal/standalone/StandaloneTerminalProcess.ts +++ b/apps/vscode/src/integrations/terminal/standalone/StandaloneTerminalProcess.ts @@ -9,6 +9,7 @@ */ import { telemetryService } from "@services/telemetry" +import { getHostProvidedShell } from "@utils/shell" import { ChildProcess, spawn } from "child_process" import { EventEmitter } from "events" import { terminateProcessTree } from "@/utils/process-termination" @@ -308,6 +309,12 @@ export class StandaloneTerminalProcess extends EventEmitter { // Clear environment variables for a clean test delete process.env.SHELL delete process.env.COMSPEC + delete process.env[HOST_SHELL_ENV_VAR] // Default userInfo() mock ;(userInfo as any) = () => ({ shell: null }) @@ -49,6 +50,42 @@ describe("Shell Detection Tests", () => { ;(userInfo as any) = originalUserInfo }) + // -------------------------------------------------------------------------- + // Host-Provided Shell Override (CLINE_TERMINAL_SHELL_PATH) + // -------------------------------------------------------------------------- + describe("Host-Provided Shell Override", () => { + it("wins over VS Code config, userInfo, and COMSPEC on Windows", () => { + Object.defineProperty(process, "platform", { value: "win32" }) + mockVsCodeConfig("windows", "PowerShell", { + PowerShell: { path: "C:\\Program Files\\PowerShell\\7\\pwsh.exe" }, + }) + ;(userInfo as any) = () => ({ shell: "C:\\Custom\\PowerShell.exe" }) + process.env.COMSPEC = "C:\\Windows\\System32\\cmd.exe" + process.env[HOST_SHELL_ENV_VAR] = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + + expect(getShell()).to.equal("C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe") + }) + + it("wins over userInfo and SHELL on POSIX", () => { + Object.defineProperty(process, "platform", { value: "darwin" }) + vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any + ;(userInfo as any) = () => ({ shell: "/bin/zsh" }) + process.env.SHELL = "/bin/bash" + process.env[HOST_SHELL_ENV_VAR] = "/usr/local/bin/fish" + + expect(getShell()).to.equal("/usr/local/bin/fish") + }) + + it("is ignored when blank", () => { + Object.defineProperty(process, "platform", { value: "darwin" }) + vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any + ;(userInfo as any) = () => ({ shell: "/bin/zsh" }) + process.env[HOST_SHELL_ENV_VAR] = " " + + expect(getShell()).to.equal("/bin/zsh") + }) + }) + // -------------------------------------------------------------------------- // Windows Shell Detection // -------------------------------------------------------------------------- diff --git a/apps/vscode/src/utils/shell.ts b/apps/vscode/src/utils/shell.ts index 7f1680b034..7be4d46528 100644 --- a/apps/vscode/src/utils/shell.ts +++ b/apps/vscode/src/utils/shell.ts @@ -43,6 +43,28 @@ interface LinuxTerminalProfile { type LinuxTerminalProfiles = Record +// ----------------------------------------------------- +// 0) Host-Provided Shell Override +// ----------------------------------------------------- + +/** + * Environment variable through which host integrations (e.g. the JetBrains + * plugin) forward the user's IDE terminal profile shell to cline-core. + * + * A dedicated variable is used instead of overriding COMSPEC/SHELL because + * COMSPEC is an OS-level contract that must keep pointing at cmd.exe: + * cross-spawn, Node's `shell: true`, and npm `.cmd` shims all pass cmd-style + * `/d /s /c` arguments to whatever COMSPEC references. Overriding it with + * PowerShell breaks every such child process (see cline/cline#11290). + */ +export const HOST_SHELL_ENV_VAR = "CLINE_TERMINAL_SHELL_PATH" + +/** Returns the shell explicitly forwarded by the host IDE, or null. */ +export function getHostProvidedShell(): string | null { + const shell = process.env[HOST_SHELL_ENV_VAR]?.trim() + return shell || null +} + // ----------------------------------------------------- // 1) VS Code Terminal Configuration Helpers // ----------------------------------------------------- @@ -299,6 +321,12 @@ export function getShellForProfile(profileId: string): string { // ----------------------------------------------------- export function getShell(): string { + // 0. A shell explicitly forwarded by the host IDE wins over detection. + const hostShell = getHostProvidedShell() + if (hostShell) { + return hostShell + } + // 1. Check VS Code config first. if (process.platform === "win32") { // Special logic for Windows