Repository navigation
Description
Bug
opencode 1.16.x (1.16.0, 1.16.1, 1.16.2) exits cleanly with code 0 and zero stderr/stdout output when its serve subcommand is spawned from a parent process as a detached child with stdio: ["ignore", "pipe", "pipe"]. The same invocation runs perfectly when launched manually in a TTY.
Impact
Affects every host that spawns opencode serve non-interactively: IDE plugins, agent orchestrators (Paseo, Claude Code, Codex sub-agents, etc.), CI runners, and test harnesses that use Node child_process.spawn or Bun.spawn with detached stdio.
Environment
- opencode: 1.16.0, 1.16.1, 1.16.2 (all released 2026-06-05)
- Hosts observed: Paseo daemon (Node.js, child_process.spawn) on Linux aarch64
- Opencode working versions: 1.15.13 and earlier
Repro
import { spawn } from "node:child_process";
const child = spawn("opencode", ["serve", "--port", "4096"], {
detached: true,
stdio: ["ignore", "pipe", "pipe"],
env: process.env, // include HOME, PATH, etc.
});
child.stdout.on("data", (b) => process.stdout.write(`[stdout] ${b}`));
child.stderr.on("data", (b) => process.stdout.write(`[stderr] ${b}`));
child.on("exit", (code, signal) => {
console.log(`exit: code=${code} signal=${signal}`); // exit: code=0 signal=null within ~2s
});With --print-logs added to the spawn args, the opencode log shows the boot sequence starting (config load, plugin load) and then the process exits before printing "listening on…". No panic, no error message — the exit is silent and the exit code is 0.
Workaround
opencode serve --port N --pure works. --pure is documented as "run without external plugins". This is a sufficient workaround for headless hosts because:
- Hosts that spawn
opencode serverarely need plugin-loaded agents — they drive the server via the HTTP API. - Plugin-registered MCPs and skills can be configured at the host level instead.
Root cause hypothesis
Likely in the plugin loader or InstanceStore.boot (cf. #25457 for a similar plugin-load regression in 1.14.32). The silent exit pattern matches an early-return on a precondition that fails when the parent process has detached the child with stdin ignored: any code path that calls process.stdin.isTTY, depends on Node's AsyncLocalStorage propagation through a process.on("exit") handler in the parent, or relies on a TTY-shaped TTY in Plugin.init would trip here. Suggests investigating:
packages/opencode/src/project/instance.ts—Instance.provide/bootpackages/opencode/src/plugin/index.ts(or equivalent) — plugin init entry- Any
process.stdinorprocess.stdout.isTTYreads during plugin init
The fact that --pure (which skips the entire plugin path) fixes the bug localizes the regression to the plugin loader, not to the server itself.
Cross-references
- Paseo workaround: fix(opencode): pass --pure to spawned opencode serve to avoid silent exit on 1.16.x getpaseo/paseo#1363
- Related: opencode-auto-exit #26149 (similar "exit code 0, silent" pattern in OpenCode Desktop), v1.14.32 regression: plugin agents and MCPs silently fail to register (ALS context lost during InstanceStore.boot) #25457 (closed plugin-load regression, 1.14.32)
Activity
shrijit-ctrl commentedon Jun 5, 2026
Paseo mitigation PR: getpaseo/paseo#1363
github-actions commentedon Aug 5, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.