From d0fa9d5a5f6574784e3818d40c217c615b02553b Mon Sep 17 00:00:00 2001 From: Yifan <74950140+geekifan@users.noreply.github.com> Date: Thu, 16 Apr 2026 21:31:07 +0800 Subject: [PATCH] fix(vscode): avoid POSIX login shell opencode detection (#921) --- packages/vscode/src/opencode.ts | 107 ++++++++++++++++---------------- 1 file changed, 55 insertions(+), 52 deletions(-) diff --git a/packages/vscode/src/opencode.ts b/packages/vscode/src/opencode.ts index 5ff50fe5..dd628ff5 100644 --- a/packages/vscode/src/opencode.ts +++ b/packages/vscode/src/opencode.ts @@ -127,6 +127,34 @@ function appendToPath(dir: string) { process.env.PATH = [trimmed, ...parts].join(path.delimiter); } +function findExecutableInPath(binaryName: string): string | null { + const trimmed = (binaryName || '').trim(); + if (!trimmed) { + return null; + } + + const current = process.env.PATH || ''; + if (!current) { + return null; + } + + for (const segment of current.split(path.delimiter)) { + const dir = segment.trim(); + if (!dir) { + continue; + } + + const candidate = path.join(dir, trimmed); + if (isExecutable(candidate)) { + return candidate; + } + } + + return null; +} + +let cachedDetectedOpencodeCliPath: string | undefined; + function resolveOpencodeCliPath(): string | null { const configured = (() => { try { @@ -185,11 +213,20 @@ function resolveOpencodeCliPath(): string | null { } } + if (cachedDetectedOpencodeCliPath) { + if (isExecutable(cachedDetectedOpencodeCliPath)) { + return cachedDetectedOpencodeCliPath; + } + cachedDetectedOpencodeCliPath = undefined; + } + const home = os.homedir(); const unixFallbacks = [ path.join(home, '.opencode', 'bin', 'opencode'), path.join(home, '.bun', 'bin', 'opencode'), path.join(home, '.local', 'bin', 'opencode'), + '/usr/local/bin/opencode', + '/opt/homebrew/bin/opencode', path.join(home, 'bin', 'opencode'), ]; @@ -214,9 +251,18 @@ function resolveOpencodeCliPath(): string | null { ].filter(Boolean); })(); + if (process.platform !== 'win32') { + const fromPath = findExecutableInPath('opencode'); + if (fromPath) { + cachedDetectedOpencodeCliPath = fromPath; + return fromPath; + } + } + const fallbacks = process.platform === 'win32' ? winFallbacks : unixFallbacks; for (const candidate of fallbacks) { if (isExecutable(candidate)) { + cachedDetectedOpencodeCliPath = candidate; return candidate; } } @@ -233,26 +279,8 @@ function resolveOpencodeCliPath(): string | null { .map((line) => line.trim()) .filter(Boolean); const found = lines.find((line) => isExecutable(line)); - if (found) return found; - } - } catch { - // ignore - } - return null; - } - - // Non-Windows: try a login shell PATH lookup. - const shells = [process.env.SHELL, '/bin/zsh', '/bin/bash', '/bin/sh'].filter(Boolean) as string[]; - for (const shell of shells) { - if (!isExecutable(shell)) continue; - try { - const result = spawnSync(shell, ['-lic', 'command -v opencode'], { - encoding: 'utf8', - stdio: ['ignore', 'pipe', 'pipe'], - }); - if (result.status === 0) { - const found = (result.stdout || '').trim().split(/\s+/).pop() || ''; - if (found && isExecutable(found)) { + if (found) { + cachedDetectedOpencodeCliPath = found; return found; } } @@ -377,40 +405,15 @@ function getLoginShellEnvSnapshot(): Record | null { return cachedLoginShellEnvSnapshot; } - if (process.platform === 'win32') { - const windowsSnapshot = getWindowsShellEnvSnapshot(); - cachedLoginShellEnvSnapshot = windowsSnapshot; - return windowsSnapshot; + // Avoid interactive POSIX login shells in the extension host. + if (process.platform !== 'win32') { + cachedLoginShellEnvSnapshot = null; + return null; } - const shellCandidates = [process.env.SHELL, '/bin/zsh', '/bin/bash', '/bin/sh'].filter(Boolean) as string[]; - for (const shellPath of shellCandidates) { - if (!isExecutable(shellPath)) { - continue; - } - - try { - const result = spawnSync(shellPath, ['-lic', 'env -0'], { - encoding: 'utf8', - stdio: ['ignore', 'pipe', 'pipe'], - maxBuffer: 10 * 1024 * 1024, - windowsHide: true, - }); - if (result.status !== 0) { - continue; - } - const parsed = parseNullSeparatedEnvSnapshot(result.stdout || ''); - if (parsed) { - cachedLoginShellEnvSnapshot = parsed; - return parsed; - } - } catch { - continue; - } - } - - cachedLoginShellEnvSnapshot = null; - return null; + const windowsSnapshot = getWindowsShellEnvSnapshot(); + cachedLoginShellEnvSnapshot = windowsSnapshot; + return windowsSnapshot; } function mergePathValues(preferred: string, fallback: string): string {