You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[FEATURE]: opencode serve should propagate SIGTERM to its child processes #20899
When using opencode serve programmatically via the SDK, killing the main server process with SIGTERM leaves an orphaned child process behind.
opencode serve spawns a child process internally. When the parent receives SIGTERM and exits, the child is reparented to PID 1 (launchd/init) rather than being killed alongside the parent. Over multiple invocations this accumulates orphaned processes.
Reproduction
import{spawn}from'node:child_process';constproc=spawn('opencode',['serve','--hostname=127.0.0.1','--port=12345']);// Later, when shutting down:proc.kill('SIGTERM');// kills the parent — child survives as orphan
After termination, pgrep -f "opencode serve" still returns a live PID.
Root cause (from the consumer side)
opencode serve spawns at least one child process. The parent and child are in the same process group, so SIGTERM sent to the parent PID does not propagate to the child. The child is then reparented to PID 1.
Workaround (consumer side)
We worked around this by spawning with detached: true (making opencode a process group leader) and killing with process.kill(-pid, 'SIGTERM') to target the entire group:
constproc=spawn('opencode',['serve', ...],{detached: true});// ...process.kill(-proc.pid,'SIGTERM');// kills parent + all children
This works, but it requires SDK consumers to know about opencode's internal process structure — which is an implementation detail they shouldn't need to care about.
Requested change
When opencode serve receives SIGTERM, it should forward the signal to all its child processes before exiting. This is standard practice for supervisor/daemon processes and would make the server safe to kill with a single SIGTERM to the main PID.
Alternatively, documenting that consumers must kill the process group (and exposing the PGID if needed) would also be acceptable.
These cover similar ground (child processes not cleaned up on signal exit), though from different entry points (TUI, desktop app). Your issue is specifically about opencode serve via the SDK, which may warrant a separate fix, but consolidation might be worth considering.
I tracked this down to the npm wrapper at packages/opencode/bin/opencode, not the serve implementation itself.
The wrapper uses blocking spawnSync() around the real .opencode binary. When a supervisor sends SIGTERM to the wrapper, the wrapper dies without forwarding the signal and the real .opencode serve child is reparented to PID 1.
So this is a simple wrapper fix: replace spawnSync() with async spawn(), forward termination signals to the child, and preserve signal-based exit semantics.
Activity
github-actions commentedon Apr 3, 2026
This issue might be a duplicate of existing issues. Please check:
These cover similar ground (child processes not cleaned up on signal exit), though from different entry points (TUI, desktop app). Your issue is specifically about
opencode servevia the SDK, which may warrant a separate fix, but consolidation might be worth considering.fix(desktop): serve frontend via localhost plugin, reliable process c…
chubes4 commentedon May 8, 2026
I tracked this down to the npm wrapper at
packages/opencode/bin/opencode, not theserveimplementation itself.The wrapper uses blocking
spawnSync()around the real.opencodebinary. When a supervisor sendsSIGTERMto the wrapper, the wrapper dies without forwarding the signal and the real.opencode servechild is reparented to PID 1.I opened a fix here: #26259
Hard A/B evidence with
homeboy observe:8d26c4f6-4e90-459b-a15d-49e76cc0f9dabdc45e95-2ec3-44bf-b50a-c7daee37552eSignals tested in both cases:
SIGTERMSIGINTSIGHUPSo this is a simple wrapper fix: replace
spawnSync()with asyncspawn(), forward termination signals to the child, and preserve signal-based exit semantics.