From 8488dc7014e10ce6ad32dac2f7e0caa9514f5477 Mon Sep 17 00:00:00 2001 From: Christian Rey Villablanca <43104891+chryzxc@users.noreply.github.com> Date: Wed, 29 Jul 2026 14:03:18 +0800 Subject: [PATCH 1/3] fix: resolve OpenCode CLI outside shell PATH --- src/services/OpencodeServerManager.ts | 71 ++++++++++++++++++++++----- 1 file changed, 60 insertions(+), 11 deletions(-) diff --git a/src/services/OpencodeServerManager.ts b/src/services/OpencodeServerManager.ts index 47dae2d..69f1a6f 100644 --- a/src/services/OpencodeServerManager.ts +++ b/src/services/OpencodeServerManager.ts @@ -57,6 +57,8 @@ import * as vscode from "vscode"; import * as cp from "child_process"; import * as net from "net"; import * as fs from "fs"; +import * as os from "os"; +import * as path from "path"; import { createOpencodeClient, OpencodeClient } from "@opencode-ai/sdk/v2"; import { createLogger } from "../utils/Logger"; import { LoggingCategories } from "../utils/LoggingSchema"; @@ -308,23 +310,38 @@ export class OpencodeServerManager { return this.opencodeBinaryPath; } - let opencodeBinary = "opencode"; + const configuredBinary = vscode.workspace + .getConfiguration("opencode") + .get("cliPath", "") + .trim(); + if (configuredBinary) { + this.opencodeBinaryPath = this.normalizeWindowsExecutablePath(configuredBinary); + log.info("Using configured opencode CLI path", { + path: this.opencodeBinaryPath, + }); + return this.opencodeBinaryPath; + } + + let opencodeBinary: string | undefined; try { - const resolverCommand = - process.platform === "win32" ? "where opencode" : "which opencode"; - const resolverResult = cp - .execSync(resolverCommand, { + const resolverResult = cp.execFileSync( + process.platform === "win32" + ? "where" + : process.platform === "darwin" + ? "/bin/zsh" + : "/bin/sh", + process.platform === "win32" ? ["opencode"] : ["-lc", "command -v opencode"], + { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"], - }) + }, + ) .trim() .split(/\r?\n/) .map((line) => line.trim()) .filter(Boolean); if (resolverResult.length > 0) { - opencodeBinary = this.normalizeWindowsExecutablePath( - resolverResult[0], - ); + opencodeBinary = this.normalizeWindowsExecutablePath(resolverResult[0]); log.debug("Resolved opencode binary path", { path: opencodeBinary }); } } catch (error) { @@ -334,8 +351,40 @@ export class OpencodeServerManager { ); } - this.opencodeBinaryPath = opencodeBinary; - return opencodeBinary; + if (!opencodeBinary) { + const home = os.homedir(); + const candidatePaths = process.platform === "win32" + ? [ + path.join(home, ".opencode", "bin", "opencode.exe"), + path.join(home, ".bun", "bin", "opencode.exe"), + path.join(home, "AppData", "Roaming", "npm", "opencode.cmd"), + ] + : [ + path.join(home, ".opencode", "bin", "opencode"), + path.join(home, ".bun", "bin", "opencode"), + path.join(home, ".local", "bin", "opencode"), + "/opt/homebrew/bin/opencode", + "/usr/local/bin/opencode", + "/usr/bin/opencode", + ]; + const existingCandidate = candidatePaths.find((candidate) => { + try { + fs.accessSync(candidate, fs.constants.X_OK); + return true; + } catch { + return false; + } + }); + if (existingCandidate) { + opencodeBinary = this.normalizeWindowsExecutablePath(existingCandidate); + log.info("Resolved opencode CLI from fallback install locations", { + path: opencodeBinary, + }); + } + } + + this.opencodeBinaryPath = opencodeBinary ?? "opencode"; + return this.opencodeBinaryPath; } private probeOpencodeBinaryVersion(binaryPath: string): string | undefined { From a14f58d34a5b3f42186000dd1687d8ff72e7ecf6 Mon Sep 17 00:00:00 2001 From: Christian Rey Villablanca <43104891+chryzxc@users.noreply.github.com> Date: Wed, 29 Jul 2026 14:03:21 +0800 Subject: [PATCH 2/3] feat: allow configuring the OpenCode CLI path --- package.json | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 3b04ebf..615255f 100644 --- a/package.json +++ b/package.json @@ -183,12 +183,17 @@ "configuration": { "title": "OpenCode", "properties": { - "opencode.serverPort": { - "type": "number", - "default": 0, - "description": "Port for OpenCode server (0 = auto-assign)" - }, - "opencode.autoStart": { + "opencode.serverPort": { + "type": "number", + "default": 0, + "description": "Port for OpenCode server (0 = auto-assign)" + }, + "opencode.cliPath": { + "type": "string", + "default": "", + "description": "Optional absolute path to the OpenCode CLI executable when it is not visible to the VS Code extension host PATH." + }, + "opencode.autoStart": { "type": "boolean", "default": true, "description": "Automatically start OpenCode server on activation" From e94b29bb72e12d84697eda2ea2645ee187af1d62 Mon Sep 17 00:00:00 2001 From: Christian Rey Villablanca <43104891+chryzxc@users.noreply.github.com> Date: Wed, 29 Jul 2026 14:03:25 +0800 Subject: [PATCH 3/3] test: cover GUI CLI path resolution --- .../opencode-version-compatibility.test.mjs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/services/opencode-version-compatibility.test.mjs b/tests/services/opencode-version-compatibility.test.mjs index 955422b..850090b 100644 --- a/tests/services/opencode-version-compatibility.test.mjs +++ b/tests/services/opencode-version-compatibility.test.mjs @@ -85,3 +85,21 @@ test("server manager still probes the CLI after a failed SDK health request", () "a healthy compatibility endpoint must not bypass the CLI version fallback", ); }); + +test("server manager resolves CLI paths for GUI-launched VS Code hosts", () => { + assert.match( + serverManagerSource, + /getConfiguration\("opencode"\)[\s\S]*get\("cliPath"/, + "users must be able to configure an absolute CLI path", + ); + assert.match( + serverManagerSource, + /command -v opencode/, + "CLI lookup should also load the shell command path used outside VS Code", + ); + assert.match( + serverManagerSource, + /\.opencode[\s\S]*\.bun[\s\S]*\.local[\s\S]*homebrew/, + "CLI lookup should cover standard user and Homebrew install locations", + ); +});