From 416a737d76e654d2fc649206c2b921a7db150775 Mon Sep 17 00:00:00 2001 From: Haileyesus <118998054+blackmammoth@users.noreply.github.com> Date: Fri, 12 Jun 2026 22:45:22 +0300 Subject: [PATCH] fix(opencode): pass workspace dir explicitly The remote environment could start OpenCode runs under /opt/claudecodeui. That happened even when the selected project path was correct. The integration relied on child-process cwd alone. OpenCode run resolves its workspace through the explicit --dir contract. Pass --dir with the resolved working directory. Assert in the CLI test that launch args include the workspace dir. --- server/opencode-cli.js | 4 ++++ server/opencode-cli.test.js | 21 ++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/server/opencode-cli.js b/server/opencode-cli.js index 237371ba..e8446329 100644 --- a/server/opencode-cli.js +++ b/server/opencode-cli.js @@ -194,6 +194,10 @@ async function spawnOpenCode(command, options = {}, ws) { void providerModelsService.resolveResumeModel('opencode', sessionId, model).then((resolvedModel) => { const args = ['run', '--format', 'json']; + // OpenCode's `run` command owns workspace selection through `--dir`. + // Relying on the child-process cwd alone is not enough on Linux, where + // the CLI can still resolve the session under the server install dir. + args.push('--dir', workingDir); if (sessionId) { args.push('--session', sessionId); } diff --git a/server/opencode-cli.test.js b/server/opencode-cli.test.js index 082451c3..acac202c 100644 --- a/server/opencode-cli.test.js +++ b/server/opencode-cli.test.js @@ -1,5 +1,5 @@ import assert from 'node:assert/strict'; -import { chmod, mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { chmod, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import test from 'node:test'; @@ -12,6 +12,11 @@ const findEnvKey = (name) => async function createFakeOpenCodeExecutable(binDir) { const scriptPath = path.join(binDir, 'opencode.js'); await writeFile(scriptPath, ` +const capturePath = process.env.OPENCODE_ARGS_CAPTURE; +if (capturePath) { + require('node:fs').writeFileSync(capturePath, JSON.stringify(process.argv.slice(2))); +} + const events = [ { type: 'text', sessionID: 'open-live-1', text: 'assistant response' }, { type: 'step_finish', sessionID: 'open-live-1' }, @@ -35,10 +40,12 @@ for (const event of events) { test('spawnOpenCode emits session_created before normalized live messages for new sessions', async () => { const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'opencode-cli-live-')); + const argsCapturePath = path.join(tempRoot, 'opencode-args.json'); const pathKey = findEnvKey('PATH'); const pathExtKey = findEnvKey('PATHEXT'); const previousPath = process.env[pathKey]; const previousPathExt = process.env[pathExtKey]; + const previousArgsCapture = process.env.OPENCODE_ARGS_CAPTURE; const messages = []; const writer = { userId: null, @@ -54,6 +61,7 @@ test('spawnOpenCode emits session_created before normalized live messages for ne try { await createFakeOpenCodeExecutable(tempRoot); process.env[pathKey] = `${tempRoot}${path.delimiter}${previousPath || ''}`; + process.env.OPENCODE_ARGS_CAPTURE = argsCapturePath; if (process.platform === 'win32') { process.env[pathExtKey] = previousPathExt?.toUpperCase().includes('.CMD') ? previousPathExt @@ -77,6 +85,11 @@ test('spawnOpenCode emits session_created before normalized live messages for ne assert.equal(streamEnd?.sessionId, 'open-live-1'); assert.equal(complete?.sessionId, 'open-live-1'); assert.equal(messages.some((message) => message.kind === 'error'), false); + + const launchedArgs = JSON.parse(await readFile(argsCapturePath, 'utf8')); + assert.ok(Array.isArray(launchedArgs)); + assert.deepEqual(launchedArgs.slice(0, 4), ['run', '--format', 'json', '--dir']); + assert.equal(launchedArgs[4], tempRoot); } finally { if (previousPath === undefined) { delete process.env[pathKey]; @@ -90,6 +103,12 @@ test('spawnOpenCode emits session_created before normalized live messages for ne process.env[pathExtKey] = previousPathExt; } + if (previousArgsCapture === undefined) { + delete process.env.OPENCODE_ARGS_CAPTURE; + } else { + process.env.OPENCODE_ARGS_CAPTURE = previousArgsCapture; + } + await rm(tempRoot, { recursive: true, force: true }); } });