diff --git a/packages/web/server/lib/opencode/env-runtime.js b/packages/web/server/lib/opencode/env-runtime.js index 892fb1ba..a9b919cf 100644 --- a/packages/web/server/lib/opencode/env-runtime.js +++ b/packages/web/server/lib/opencode/env-runtime.js @@ -362,6 +362,30 @@ export const createOpenCodeEnvRuntime = (deps) => { return null; } + // Fast path: 'command -v' via plain sh (no login shell, no .zshrc sourcing). + // This is much faster than the full login shell probe below and catches + // standard brew paths even when launched with minimal PATH. + if (process.platform !== 'win32') { + try { + const fastResult = runSpawnSync('/bin/sh', ['-c', 'command -v opencode'], { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'], + windowsHide: true, + timeout: SHELL_PROBE_TIMEOUT_MS, + }); + if (fastResult.status === 0) { + const found = (fastResult.stdout || '').trim().split(/\s+/).pop() || ''; + if (found && isExecutable(found)) { + clearWslOpencodeResolution(); + state.resolvedOpencodeBinarySource = 'shell'; + return found; + } + } + } catch { + // Fall through to login shell probe + } + } + const shells = [process.env.SHELL, '/bin/zsh', '/bin/bash', '/bin/sh'].filter(Boolean); for (const shell of shells) { if (!isExecutable(shell)) continue; @@ -430,6 +454,28 @@ export const createOpenCodeEnvRuntime = (deps) => { return null; } + // Fast path: 'command -v' via plain sh (no login shell, no .zshrc sourcing). + // This is much faster than the full login shell probe below and catches + // standard brew paths even when launched with minimal PATH. + if (process.platform !== 'win32') { + try { + const fastResult = runSpawnSync('/bin/sh', ['-c', 'command -v node'], { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'], + windowsHide: true, + timeout: SHELL_PROBE_TIMEOUT_MS, + }); + if (fastResult.status === 0) { + const found = (fastResult.stdout || '').trim().split(/\s+/).pop() || ''; + if (found && isExecutable(found)) { + return found; + } + } + } catch { + // Fall through to login shell probe + } + } + const shells = [process.env.SHELL, '/bin/zsh', '/bin/bash', '/bin/sh'].filter(Boolean); for (const shell of shells) { if (!isExecutable(shell)) continue; @@ -512,6 +558,28 @@ export const createOpenCodeEnvRuntime = (deps) => { return null; } + // Fast path: 'command -v' via plain sh (no login shell, no .zshrc sourcing). + // This is much faster than the full login shell probe below and catches + // standard brew paths even when launched with minimal PATH. + if (process.platform !== 'win32') { + try { + const fastResult = runSpawnSync('/bin/sh', ['-c', 'command -v bun'], { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'], + windowsHide: true, + timeout: SHELL_PROBE_TIMEOUT_MS, + }); + if (fastResult.status === 0) { + const found = (fastResult.stdout || '').trim().split(/\s+/).pop() || ''; + if (found && isExecutable(found)) { + return found; + } + } + } catch { + // Fall through to login shell probe + } + } + const shells = [process.env.SHELL, '/bin/zsh', '/bin/bash', '/bin/sh'].filter(Boolean); for (const shell of shells) { if (!isExecutable(shell)) continue;