Skip to content

server: stdio MCP child processes not terminated on service restart / config reload #50758

Description

@DonTradeLab

Summary

When the background server restarts — or when an MCP config change triggers a reload — the processes of local (stdio) MCP servers are not terminated. Old generations linger as orphans while a new generation starts, leaking processes/memory. Under CPU contention this cascades: the client↔server event stream stalls, the server restarts, and another full set of MCP processes is spawned.

Environment

  • opencode version: 2.0.14
  • OS: Linux 6.8.0-138-generic (x86_64)
  • Terminal: tmux (TERM=xterm-256color, TERM_PROGRAM=tmux)
  • Shell: /bin/bash
  • Install/channel: release
  • Active plugins: local plugins loaded from ~/.config/opencode/plugins/

Reproduction

  1. Configure several local MCP servers (mcp.servers, type: "local") in opencode.json.
  2. Start the background service and let the MCP servers connect.
  3. Run opencode service restart (or change one MCP entry to trigger a reload).
  4. Inspect the process table: the previous MCP child processes remain alive; a new set is started.

Expected Behavior

The server should terminate its MCP child processes on restart/shutdown (preferably via a process group / cgroup kill) and reconnect cleanly, leaving a single set.

Actual Behavior

Old stdio MCP processes survive as orphans. After switching one MCP in the config, several local servers reported failed: Connection closed until a full restart — with their processes still alive. Under CPU contention we observed 7 overlapping MCP generations within ~86 s (~120 orphan processes) and 7 Event stream stalled entries; the server restarted repeatedly.

Additional Context

  • opencode mcp list right after the reload: one server connected, the rest failed: Connection closed, with the processes still present in ps.
  • Log: Event stream stalled followed by All fibers interrupted at ServerProcess.start (~/.local/share/opencode/log/opencode.log).
  • Aggravating architecture: runs using --standalone start a private server per execution, each with its own copy of the local MCPs — multiplication by design (the MCP spec states stdio serves one client, HTTP serves many).
  • Workaround adopted: (a) automated runs use the shared service instead of --standalone; (b) heavy MCPs run as long-lived services behind an HTTP proxy (Streamable HTTP).
  • Host: 4 vCPU, 8 GB; load reached 11. The systemd-managed service itself stayed up (NRestarts=0).

Activity

  1. github-actions commented on Sep 22, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    If this covers a distinct scenario (specifically config-reload-triggered restarts cascading under CPU contention), it may still warrant its own tracking, but please review the above issues first to see if your report fits better there or can be consolidated.

  2. Tyxiel commented on Sep 29, 2026

    @Tyxiel

    Adding a data point that suggests a second trigger beyond restart/config-reload, and it may be the more important one.

    In my case none of the triggers in the original report happened: no opencode service restart, no config change, no MCP entry edited. The app was simply left open, and the server reconnected the same MCP server on its own every 3–5 minutes.

    Reconnect timestamps, last 12, all from a single server instance (run=4482db38):

    10:44:35  10:46:04  10:48:48  11:08:10  11:12:00  11:25:08
    11:25:59  11:30:10  11:33:07  11:33:31  11:35:11  11:35:17
    

    And the accounting over the whole log:

    mcp connected server=codegraph : 132
    mcp disconnected               :   2
    

    ~66 reconnects per disconnect. I searched the log for mcp disconnected, mcp closed, dispose, and cleanup — there is no per-connection teardown path, so each reconnect leaves a full 4-process tree (cmd.exe → node → node --liftoff-only → watchdog node) behind with its parent still alive.

    Measured growth over 8 minutes with no user action: 1.478 GB / 16 processes → 2.811 GB / 30 processes, roughly 370 MB per reconnect.

    Possible causal chain — the reconnect may not be the root cause but a symptom. The codegraph CLI ships a self-watchdog whose command line contains:

    function kill(extra) { /* "Main thread unresponsive for ~Ns" */ process.kill(parentPid, 'SIGKILL'); }

    with timeoutMs=60000 and capMs=600000. So the main codegraph thread is being detected as wedged, the watchdog kills it, and opencode reconnects — then fails to dispose the dead tree.

    If that holds, the missing dispose is still the thing to fix here, but there may be a companion fix needed so the watchdog death is handled as a clean shutdown rather than triggering a reconnect that stacks a new tree.

    Related: #46174 (Windows, multi-GB, same shape) — I have the fuller process census and the 132-vs-2 connect/disconnect accounting there.

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