Skip to content

opencode 1.16.x exits silently (code 0) when spawned detached with stdio[0]="ignore" and no controlling tty #31006

Closed as not planned

Description

@shrijit-ctrl

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:

  1. Hosts that spawn opencode serve rarely need plugin-loaded agents — they drive the server via the HTTP API.
  2. 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 / boot
  • packages/opencode/src/plugin/index.ts (or equivalent) — plugin init entry
  • Any process.stdin or process.stdout.isTTY reads 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

Activity

  1. shrijit-ctrl commented on Jun 5, 2026

    @shrijit-ctrl
    Author

    Paseo mitigation PR: getpaseo/paseo#1363

  2. github-actions commented on Aug 5, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions