From fc012cd290f0827228a94e4b3d27428fbede2df7 Mon Sep 17 00:00:00 2001 From: Adam Mansfield Date: Sat, 18 Apr 2026 17:35:59 -0400 Subject: [PATCH 1/2] fix: opencode is not on PATH on Windows (#2163) --- .../src/provider/opencodeRuntime.test.ts | 19 +++++++++++++++++++ apps/server/src/provider/opencodeRuntime.ts | 13 +++++++++++++ 2 files changed, 32 insertions(+) diff --git a/apps/server/src/provider/opencodeRuntime.test.ts b/apps/server/src/provider/opencodeRuntime.test.ts index 0ea63f8d534..3ebf6c11bc7 100644 --- a/apps/server/src/provider/opencodeRuntime.test.ts +++ b/apps/server/src/provider/opencodeRuntime.test.ts @@ -7,6 +7,9 @@ const childProcessMock = vi.hoisted(() => ({ if (command === "which" && args[0] === "opencode") { return "/opt/homebrew/bin/opencode\n"; } + if (command === "where.exe" && args[0] === "opencode") { + return "C:\\Users\\testuser\\.bun\\bin\\opencode.exe\r\n"; + } return ""; }), spawn: vi.fn(), @@ -25,6 +28,22 @@ describe("resolveOpenCodeBinaryPath", () => { it("resolves command names through PATH", async () => { const { resolveOpenCodeBinaryPath } = await import("./opencodeRuntime.ts"); + if (process.platform === "win32") { + assert.equal( + resolveOpenCodeBinaryPath("opencode"), + "C:\\Users\\testuser\\.bun\\bin\\opencode.exe", + ); + assert.deepEqual(childProcessMock.execFileSync.mock.calls[0], [ + "where.exe", + ["opencode"], + { + encoding: "utf8", + timeout: 3_000, + }, + ]); + return; + } + assert.equal(resolveOpenCodeBinaryPath("opencode"), "/opt/homebrew/bin/opencode"); assert.deepEqual(childProcessMock.execFileSync.mock.calls[0], [ "which", diff --git a/apps/server/src/provider/opencodeRuntime.ts b/apps/server/src/provider/opencodeRuntime.ts index 4778f6eac91..1e4236f1641 100644 --- a/apps/server/src/provider/opencodeRuntime.ts +++ b/apps/server/src/provider/opencodeRuntime.ts @@ -304,6 +304,17 @@ export function resolveOpenCodeBinaryPath(binaryPath: string): string { if (Path.isAbsolute(binaryPath)) { return binaryPath; } + if (process.platform === "win32") { + return ( + execFileSync("where.exe", [binaryPath], { + encoding: "utf8", + timeout: 3_000, + }) + .split(/\r?\n/) + .map((s) => s.trim()) + .find((s) => s.length > 0) ?? "" + ); + } return execFileSync("which", [binaryPath], { encoding: "utf8", timeout: 3_000, @@ -361,6 +372,8 @@ export async function startOpenCodeServerProcess(input: { const args = ["serve", `--hostname=${hostname}`, `--port=${port}`]; const child = spawn(input.binaryPath, args, { stdio: ["ignore", "pipe", "pipe"], + // Windows needs shell mode to resolve opencode.cmd shim + shell: process.platform === "win32", env: { ...process.env, OPENCODE_CONFIG_CONTENT: JSON.stringify({}), From bd398d73e419685ddbe71d3a73b56ad5cd48c745 Mon Sep 17 00:00:00 2001 From: Adam Mansfield Date: Mon, 20 Apr 2026 09:16:57 -0400 Subject: [PATCH 2/2] fix: opencode is not on PATH on Windows for .cmd shim --- apps/server/src/provider/opencodeRuntime.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/server/src/provider/opencodeRuntime.ts b/apps/server/src/provider/opencodeRuntime.ts index 9f10738dbfb..bd447d0b5d2 100644 --- a/apps/server/src/provider/opencodeRuntime.ts +++ b/apps/server/src/provider/opencodeRuntime.ts @@ -331,6 +331,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () { const child = yield* spawner .spawn( ChildProcess.make(input.binaryPath, args, { + shell: process.platform === "win32", env: { ...process.env, OPENCODE_CONFIG_CONTENT: JSON.stringify({}),