Compare commits

...
Author SHA1 Message Date
Mikołaj Kondratek 5133e78c94 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() - which the SDK backend's
backgroundExec path resolves through via getShellForProfile() in
vscode-run-commands-tool - while COMSPEC/SHELL keep their OS semantics.
(The standalone terminal previously patched here was removed on main
by #11972; backgroundExec is now the only standalone exec path.)

Fixes #11290
(cherry picked from commit 602097477 on legacy-extension)
2026-07-16 19:46:03 +02:00
2 changed files with 66 additions and 1 deletions
+38 -1
View File
@@ -15,7 +15,7 @@ const osMock = () => ({ ...osMockNamespace, default: osMockNamespace })
mock.module("os", osMock)
mock.module("node:os", osMock)
import { getShell } from "@utils/shell"
import { getShell, HOST_SHELL_ENV_VAR } from "@utils/shell"
describe("Shell Detection Tests", () => {
let originalPlatform: string
@@ -49,6 +49,7 @@ describe("Shell Detection Tests", () => {
// 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
userInfoImpl = (() => ({ shell: null })) as any
@@ -62,6 +63,42 @@ describe("Shell Detection Tests", () => {
userInfoImpl = 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" },
})
userInfoImpl = (() => ({ shell: "C:\\Custom\\PowerShell.exe" })) as any
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
userInfoImpl = (() => ({ shell: "/bin/zsh" })) as any
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
userInfoImpl = (() => ({ shell: "/bin/zsh" })) as any
process.env[HOST_SHELL_ENV_VAR] = " "
expect(getShell()).to.equal("/bin/zsh")
})
})
// --------------------------------------------------------------------------
// Windows Shell Detection
// --------------------------------------------------------------------------
+28
View File
@@ -48,6 +48,28 @@ interface LinuxTerminalProfile {
type LinuxTerminalProfiles = Record<string, LinuxTerminalProfile>
// -----------------------------------------------------
// 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
// -----------------------------------------------------
@@ -305,6 +327,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