mirror of
https://github.com/cline/cline.git
synced 2026-09-11 16:42:40 +08:00
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
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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<TerminalProcessEvent
|
||||
* @returns The default shell path
|
||||
*/
|
||||
private getDefaultShell(): string {
|
||||
// A shell explicitly forwarded by the host IDE (e.g. the JetBrains
|
||||
// terminal profile) takes precedence over the OS defaults.
|
||||
const hostShell = getHostProvidedShell()
|
||||
if (hostShell) {
|
||||
return hostShell
|
||||
}
|
||||
if (process.platform === "win32") {
|
||||
return process.env.COMSPEC || "cmd.exe"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getShell } from "@utils/shell"
|
||||
import { getShell, HOST_SHELL_ENV_VAR } from "@utils/shell"
|
||||
import { expect } from "chai"
|
||||
import { afterEach, beforeEach, describe, it } from "mocha"
|
||||
import { userInfo } from "os"
|
||||
@@ -36,6 +36,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
|
||||
;(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
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
@@ -43,6 +43,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
|
||||
// -----------------------------------------------------
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user