Skip to content

[FEATURE]: opencode serve should propagate SIGTERM to its child processes #20899

Closed

Description

@kud

Problem

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';

const proc = 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:

const proc = 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.

Environment

Activity

  1. added
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on Apr 3, 2026
  2. github-actions commented on Apr 3, 2026

    @github-actions
    Contributor

    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 serve via the SDK, which may warrant a separate fix, but consolidation might be worth considering.

  3. removed
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on May 3, 2026
  4. chubes4 commented on May 8, 2026

    @chubes4
    Contributor

    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.

    I opened a fix here: #26259

    Hard A/B evidence with homeboy observe:

    Case Homeboy run Result
    Stock upstream wrapper 8d26c4f6-4e90-459b-a15d-49e76cc0f9da 3/3 orphans
    Patched wrapper from PR #26259 bdc45e95-2ec3-44bf-b50a-c7daee37552e 0/3 orphans

    Signals tested in both cases:

    • SIGTERM
    • SIGINT
    • SIGHUP

    So this is a simple wrapper fix: replace spawnSync() with async spawn(), forward termination signals to the child, and preserve signal-based exit semantics.

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