Repository navigation
Description
Bug description
When OpenCode is spawned as a subprocess (e.g. opencode run --format json) and the parent closes the read end of the stdout pipe before the child exits, the OpenCode process can enter a tight loop writing to the closed stdout and pin a CPU core at 100%.
This is the same class of broken-pipe problem that was fixed for the packaged desktop app in #25927, but it affects the CLI/server subprocess path rather than Electron's electron-log console transport.
Environment
- OpenCode version: observed on multiple recent CLI/server builds
- OS / platform: Linux (subprocess parent is
multicadaemon); likely reproducible on any platform where stdout is closed while child is alive - Launch mode:
opencode run --format jsoninvoked by a parent process
Steps to reproduce
- Launch
opencode run --format jsonfrom a parent process (e.g. a daemon or CI runner). - While OpenCode is still running, have the parent close the read end of OpenCode's stdout pipe (or terminate/restart the parent).
- Observe the OpenCode process:
- It remains alive.
strace -p <pid>shows repeatedwrite(1, ...)calls returning-1 EPIPE (Broken pipe).- CPU usage for that process rises to ~100% of one core.
- The timestamp in the repeated log line is frozen, indicating the same buffer is retried in a tight loop.
Expected behavior
If stdout/stderr is no longer writable, the OpenCode subprocess should exit cleanly instead of looping on EPIPE.
Actual behavior
The OpenCode subprocess stays alive and spins, repeatedly writing to the closed stdout and receiving EPIPE. SIGPIPE is ignored (SigIgn bit 12 set in /proc/<pid>/status), so the OS does not terminate it.
Diagnostic details
Excerpt from strace -p <pid> during the spin:
write(1, "[06:30:24.162] ERROR (#10957): failed {\n ref: \"err_d6d2d874\",\n error: { ... }", 4096) = -1 EPIPE (Broken pipe)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=1679655, si_uid=1000} ---
/proc/<pid>/status shows SIGPIPE is ignored.
Root-cause hypothesis
- OpenCode ignores
SIGPIPE, so writing to a closed stdout does not kill the process. - The write failure surfaces as an uncaught exception or stream
errorevent. - The exception handler attempts to log the error back to stdout, which is already closed, re-triggering the same error.
In packages/opencode/src/cli/tui/worker.ts the uncaughtException and unhandledRejection handlers are currently no-ops:
const onUnhandledRejection = (_error: unknown) => {}
const onUncaughtException = (_error: Error) => {}
process.on("unhandledRejection", onUnhandledRejection)
process.on("uncaughtException", onUncaughtException)While this may swallow the exception rather than recursively log it, a broken stdout leaves the process alive and looping instead of exiting cleanly.
Suggested fix
Treat EPIPE / broken-pipe errors as fatal for CLI/server subprocesses, similar to how #25927 disabled the console transport on broken pipe for the desktop build:
process.on("uncaughtException", (err) => {
if ((err as NodeJS.ErrnoException).code === "EPIPE") {
process.exit(1)
}
// ...existing handling
})Alternatively, ensure stream error handlers for stdout/stderr detect broken pipe and terminate the process rather than retrying or swallowing.
Why this is not a duplicate of #24719
- [Bug] 100% CPU usage in background opencode-cli serve process on Wayland (WebKitGTK socket drop) #24719 concerns the background desktop/
opencode serveprocess on Wayland, triggered by a WebKitGTK socket drop. - This issue concerns the
opencode runCLI subprocess when its stdout pipe is closed by an external parent (e.g. a daemon that cancels a task or restarts). - The trigger, process role, and fix location are different, even though both produce an EPIPE-driven CPU loop.
Related issues
- Packaged desktop app can crash in main process with write EPIPE from electron-log console transport #25927 — prior EPIPE fix for the desktop
electron-logconsole transport. - opencode subprocess cleanup closes stdout before process exits, can orphan child and trigger EPIPE spin multica-ai/multica#4533 — corresponding issue in the spawning parent about closing stdout before the child exits.
Activity
github-actions commentedon Jun 24, 2026
This issue doesn't fully meet our contributing guidelines.
What needs to be fixed:
Please edit this issue to address the above within 2 hours, or it will be automatically closed.
This issue might be a duplicate of existing issues. Please check:
opencode serveprocess — different trigger (Wayland WebKitGTK socket drop vs CLI stdout pipe closed), but the same symptom of an EPIPE-driven tight loop pinning CPU.If you believe this was flagged incorrectly, please let a maintainer know.
github-actions commentedon Jun 24, 2026
Thanks for updating the issue! It now meets the contributing guidelines. The bug report includes a clear description, environment details, steps to reproduce, expected vs actual behavior, and diagnostic context. Removing the compliance flag.
fix(agent): terminate opencode process group before closing stdout (#…
fix(agent): terminate opencode process group before closing stdout (m…
fix(cli): exit cleanly on closed stdout (EPIPE) instead of spinning
github-actions commentedon Aug 24, 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.