anomalyco / anomalyco/opencode

[Desktop][WSL] 2.0.2: WSL detection and install verification always fail — wsl.exe re-expands $VAR in script arguments

Open
#48,640 4 comments 0 reactions 1 assignee View on GitHub

@Hona is already working on this.

Since Sep 12, 2026.

Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

Description

中文摘要: 桌面端 2.0.2 在 Windows 上无法检测/连接 WSL(Debian)里的 opencode:即使 ~/.opencode/bin/opencode 已安装且版本与桌面端一致,界面始终显示"未安装 OpenCode";点击"安装 OpenCode"后二进制实际已装上,但 UI 报请求失败,依旧无法添加/连接 WSL 服务器。根因是桌面端把 shell 脚本作为命令行参数传给 wsl.exe(wsl -d <distro> -- sh -lc "<script>"),WSL 会在目标 sh 执行之前对参数做一次 shell 式展开($VAR 被替换、$(...) 被求值),脚本因此被破坏。改用 stdin 传脚本(或加 --exec)即可修复。详细证据见下。


English

Summary

On Windows, OpenCode Desktop 2.0.2 shows "OpenCode not installed" for a WSL2 distro even though ~/.opencode/bin/opencode (v2.0.2, matching the Desktop version) is installed and runs correctly. Clicking Install OpenCode actually installs the binary, but the request fails with a generic UnknownError: An error occurred in Effect.tryPromise, and the dialog keeps showing "OpenCode not installed" — the WSL server can never be added or connected.

Root cause: the Desktop passes shell scripts as command-line arguments to wsl.exe:

wsl -d <distro> -- sh -lc "<script>"

WSL performs a shell-style expansion pass over those arguments before the target sh receives them: $VAR tokens are expanded from the distro environment and $(...) command substitutions are executed. The scripts generated by the Desktop (RemoteCli.discoverScript, the verify step of RemoteCli.installScript) rely on runtime expansion, so they arrive already corrupted.

Environment

  • OpenCode Desktop 2.0.2 (Windows, packaged)
  • Windows 11 (build 10.0.26200)
  • WSL 2.6.1.0 (kernel 6.6.87.2-1)
  • Distro: Debian (WSL2, default, systemd=true)
  • opencode inside distro: ~/.opencode/bin/opencodeopencode v2.0.2 (same as Desktop)

Reproduction

  1. On Windows 11 with WSL2, have a Debian distro without opencode inside.
  2. Desktop 2.0.2 → Add WSL server → the distro probe succeeds, but it shows "OpenCode not installed".
  3. Click Install OpenCode: the binary is really installed (~/.opencode/bin/opencode, verified in a terminal) but the UI reports a failed request and still shows "not installed". Re-checking/relaunching does not help.

Mechanical reproduction (Windows, Node — this is exactly how the Electron main process spawns wsl):

const { spawnSync } = require("node:child_process")
const script = `cli=""
if [ -z "$cli" ] && [ -x "$HOME/.opencode/bin/opencode" ]; then cli="$HOME/.opencode/bin/opencode"; fi

if [ -n "$cli" ]; then printf '%s\\n' "$cli"; fi
`
const r = spawnSync("wsl", ["-d", "Debian", "--", "sh", "-lc", script], { encoding: "utf8" })
console.log("stdout:", JSON.stringify(r.stdout)) // => "" ; expected: /home/<user>/.opencode/bin/opencode

Evidence

Dumping /proc/$$/cmdline from inside the executed script shows what sh actually received.

Original script sent by the app:

cli=""
if [ -z "$cli" ] && [ -x "$HOME/.opencode/bin/opencode" ]; then cli="$HOME/.opencode/bin/opencode"; fi

if [ -n "$cli" ]; then printf '%s\n' "$cli"; fi

What sh actually received (variables were already expanded):

cli=""
if [ -z "" ] && [ -x "/home/<user>/.opencode/bin/opencode" ]; then cli="/home/<user>/.opencode/bin/opencode"; fi

if [ -n "" ]; then printf '%s\n' ""; fi

The final condition is always false, so resolveWslCli() always returns empty and the Desktop always reports "not installed".

The install verification step suffers the same fate:

original  : test "$("$HOME/.opencode/bin/opencode" --version | awk '{print $NF}' | sed 's/^v//')" = '2.0.2'
received  : test "" = '2.0.2'

That is why the install reports failure after the binary has been installed successfully.

No Windows-side shell is involved (child_process.spawn passes arguments verbatim) — the expansion is performed by WSL itself.

Control experiments

Same Node spawn, same distro, same script:

Invocation Result
wsl -d Debian -- sh -lc <script> (current code) empty output ❌
wsl -d Debian --exec sh -lc <script> correct path ✅
wsl -d Debian -e sh -lc <script> correct path ✅
script over stdin: wsl -d Debian -- bash -se + write to stdin correct ✅

wsl --help documents --exec, -e <CommandLine> as "Execute the specified command without using the default Linux shell"; without it, the command line runs through the default Linux shell, which is what causes the extra expansion.

Notably, spawnWslSidecar in packages/desktop/src/main/wsl/sidecar.ts already passes its launch script via stdin (bash -se), so the sidecar itself is unaffected: once detection/verification is bypassed, opencode serve starts and /api/health over 127.0.0.1 returns 200. The failure is entirely in the detect/install/verify steps.

Why this is different from existing issues

  • #38309 (Desktop 1.18.x) was about only accepting the fixed $HOME/.opencode/bin/opencode path; PRs #44514 / #44512 propose PATH-based resolution.
  • This report is a different root cause in the current v2 code: the fixed path exists and is correct, but the script that checks it is corrupted by wsl.exe argument handling. PATH-based resolution alone will not fix it.

Suggested fix

In packages/desktop/src/main/wsl/runtime.ts:

  • Preferred: stop passing scripts as wsl.exe arguments; write them to stdin (same pattern as sidecar.ts) for runWslSh / resolveWslCli / readWslCliVersion / installWslCli.
  • Minimal alternative: add --exec/-e in wslArgs() so arguments bypass the default Linux shell.
  • Add a regression test that exercises the real argv path on Windows; the current servers.test.ts mocks the probes and cannot catch this.

Workaround (user side)

Expose a distro environment variable named cli to WSL so the pre-expansion yields the right answer:

  • Windows user env: cli=/home/<user>/.opencode/bin/opencode and WSLENV=cli, then fully restart the Desktop app.

Verified locally: detection passes, the server is added, sidecar starts, health check returns 200. (Install/update may still show the generic error even though the binary gets installed.)


中文

现象

Windows 上的 OpenCode Desktop 2.0.2 中,新增 WSL 服务器对话框能识别 Debian,但始终显示 "未安装 OpenCode";实际在 WSL 里 ~/.opencode/bin/opencode 已安装且版本为 v2.0.2,与桌面端一致。点击"安装 OpenCode"后二进制确实被装上,但界面报请求失败(UnknownError: An error occurred in Effect.tryPromise),依旧无法添加/连接服务器。

根因

桌面端把 shell 脚本作为参数传给 wsl.exe:

wsl -d <distro> -- sh -lc "<script>"

WSL 会在目标 sh 收到脚本之前,先对这些参数做一次 shell 式展开:$VAR 会从发行版环境变量取值替换,$(...) 会被直接执行。而桌面端生成的脚本(RemoteCli.discoverScript、安装脚本里的版本校验)依赖运行时展开,于是到达 sh 时已经被破坏。

证据

从脚本内部 dump /proc/$$/cmdline,可以看到 sh 实际收到的内容:

原始脚本(应用发送):

if [ -n "$cli" ]; then printf '%s\n' "$cli"; fi

sh 实际收到:

if [ -n "" ]; then printf '%s\n' ""; fi

最后一个判断永远为假 → resolveWslCli() 永远返回空 → 界面永远显示"未安装"。

安装校验脚本同理:

原始: test "$("$HOME/.opencode/bin/opencode" --version | awk '{print $NF}' | sed 's/^v//')" = '2.0.2'
收到: test "" = '2.0.2'

所以"安装成功但报失败"。

对照实验

调用方式 结果
wsl -d Debian -- sh -lc <脚本>(现状) 空输出 ❌
wsl -d Debian --exec sh -lc <脚本> 正确输出路径 ✅
wsl -d Debian -e sh -lc <脚本> 正确 ✅
走 stdin(bash -se) 正确 ✅

wsl --help--exec, -e 的说明是"在不使用默认 Linux shell 的情况下执行指定的命令";不带它时命令行会经默认 shell 执行,这正是二次展开的来源。而 sidecar.ts 启动服务器时本来就是走 stdin 的,所以服务器进程本身没问题(绕过检测后,/api/health 返回 200)。

与 #38309 等的区别

#38309(1.18.x)的问题是只认固定路径 $HOME/.opencode/bin/opencode,#44514 / #44512 提议从 PATH 解析;本 issue 是另一个根因:固定路径存在且正确,但检查它的脚本被 wsl.exe 的参数处理破坏。仅做 PATH 解析不能修复本问题。

建议修复

packages/desktop/src/main/wsl/runtime.ts:

  • 首选:脚本一律改走 stdin(照 sidecar.ts 的写法),覆盖 runWslSh / resolveWslCli / readWslCliVersion / installWslCli;
  • 最小改动:wslArgs() 里加 --exec/-e;
  • 补一条真实经过 wsl.exe 参数的回归测试(现有 servers.test.ts 全是 mock,测不到)。

用户侧临时方案

在 Windows 用户环境变量里设置 WSLENV=clicli=/home/<user>/.opencode/bin/opencode,完全重启桌面端。原理:WSL 的预展开用的就是发行版环境变量,提前把路径"喂"进去。实测检测通过、可添加服务器、sidecar 正常启动、健康检查 200。


All paths and usernames in this report have been anonymized. No personal data is included.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.