Skip to content

fix: kill orphaned MCP child processes and expose OPENCODE_PID on shu…#15516

Merged
nexxeln merged 1 commit intoanomalyco:devfrom
gignit:fix/mcp-orphan-cleanup-and-pid
Mar 1, 2026
Merged

fix: kill orphaned MCP child processes and expose OPENCODE_PID on shu…#15516
nexxeln merged 1 commit intoanomalyco:devfrom
gignit:fix/mcp-orphan-cleanup-and-pid

Conversation

@ryanwyler
Copy link
Contributor

Issue for this PR

Closes #6633
Related: #7261, #14237, #6279, #15117

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

When opencode exits, MCP servers that spawn child processes (e.g. chrome-devtools-mcp spawning Chrome) leave those grandchild processes orphaned. The MCP SDK's StdioClientTransport.close() only signals the direct child — it has no way to reach processes further down the tree. These orphans accumulate across sessions and cause a 3-5 second shutdown delay because the SDK times out waiting on a server that is blocked waiting on its own children.

Additionally, concurrent opencode sessions sharing the same MCP server config clash on shared state. With chrome-devtools-mcp for example, each new session tries to take over the same browser instance, killing the previous session's MCP service in the process.

Before calling client.close(), we now walk the full descendant tree of each local MCP server transport using pgrep -P and SIGTERM every descendant. With its children already gone, the MCP server exits immediately and the SDK close completes without hitting its timeout. Skipped on Windows where pgrep is not available.

Also sets OPENCODE_PID in process.env at startup alongside the existing OPENCODE=1. MCP server configs can reference {env:OPENCODE_PID} to keep each session's MCP services fully independent — each session gets its own isolated browser instance and they no longer interfere with each other.

Example config:

"chrome-devtools": {
  "type": "local",
  "command": [
    "npx", "-y", "chrome-devtools-mcp@latest",
    "--userDataDir={env:HOME}/.cache/chrome-devtools-mcp/{env:OPENCODE_PID}"
  ]
}

Changes:

  • mcp/index.ts: descendants() walks the process tree via pgrep -P BFS; dispose handler SIGTERMs all descendants before SDK close
  • index.ts, e2e-local.ts: set process.env.OPENCODE_PID = String(process.pid)

How did you verify your code works?

Started opencode with chrome-devtools-mcp configured with --userDataDir={env:HOME}/.cache/chrome-devtools-mcp/{env:OPENCODE_PID}. Confirmed Chrome launched. Exited opencode — exit was immediate (previously 3-5 seconds). pgrep -fa chrome-devtools-mcp returned nothing. Ran two concurrent sessions and confirmed each used an isolated profile directory with no SingletonLock conflicts.

Screenshots / recordings

N/A — terminal process, no UI changes.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

The following comment was made by an LLM, it may be inaccurate:

Based on my search, I found the following potentially related PRs:

Related PRs (not duplicates)

  1. PR fix(web): dispose idle MCP server instances to prevent process accumulation #15326 - "fix(web): dispose idle MCP server instances to prevent process accumulation"

    • Addresses similar process cleanup concerns for MCP servers
  2. PR fix: resolve memory leaks and zombie processes from missing cleanup handlers #13186 - "fix: resolve memory leaks and zombie processes from missing cleanup handlers"

    • Related to zombie process cleanup and proper cleanup handlers
  3. PR fix(mcp): Fix memory leaks in OAuth transport and process cleanup #9145 - "fix(mcp): Fix memory leaks in OAuth transport and process cleanup"

    • Involves MCP process cleanup and memory leak resolution

These PRs address related issues with process cleanup and MCP server management, but none appear to be duplicates of PR #15516. The current PR is uniquely focused on:

  • Killing orphaned MCP child processes via process tree traversal (pgrep -P)
  • Exposing OPENCODE_PID for session isolation in MCP configs

No duplicate PRs found

Copy link
Contributor

@nexxeln nexxeln left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm tysm

@nexxeln nexxeln merged commit c4c0b23 into anomalyco:dev Mar 1, 2026
9 checks passed
@ryanwyler
Copy link
Contributor Author

One more note about this one, the reason I didn't add OPENCODE_SESSION_ID to be exported is because the point specifically where MCP services are initiated is BEFORE a session has been initiated. Setting/exporting SessionID for use with the MCP services would be a major overhaul compared to how it's done today, and not add much to any value in my opinion. I can see the point of setting OPENCODE_SESSION_ID as an environment variable once a session is established so that it can be used in the session or for agents or other use cases, but that is solving a different problem and different area of the code than this PR, so to keep it clean this was focused on the MCP services.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] MCP processes not terminated after session ends

2 participants