Skip to content

Post-authentication MCP client rebuild at startup leaves orphaned stdio MCP server processes #4392

Description

@michael3lyb

Describe the bug

At startup, the CLI spawns its configured MCP servers, then — once GitHub authentication completes — tears down and rebuilds the entire MCP client, re-spawning every configured server. The first generation of stdio child processes is neither killed nor reaped, so each affected session permanently leaks one orphaned MCP server process.

Remote HTTP MCP servers simply reconnect and leave no trace, so the leak is invisible unless you have a local/stdio server configured.

This appears to be a race: only servers that finish their handshake before the post-auth rebuild get orphaned. Servers still cold-starting at that moment are unaffected.

Affected version

GitHub Copilot CLI 1.0.78 (Node v24.18.1)

Steps to reproduce the behavior

My setup: one local (stdio) MCP server (a Python CLI with a slow cold start, roughly 6–50 s) plus two remote HTTP MCP servers.

  1. Configure a stdio MCP server in ~/.copilot/mcp-config.json that completes its handshake within a few seconds of launch.
  2. Start copilot interactively and wait until the session is ready.
  3. Count the child processes for that server: ps -eo pid,ppid,lstart,command | grep <server>
  4. Observe two live child processes under the same CLI pid, spawned roughly 15–50 s apart. Only the second one is actually in use.

Note on reproduction: I could not produce a minimal repro on this machine, because my org's managed MCP allow list rejects any server not on the corporate whitelist (Skipping MCP server "<name>": not permitted by enterprise managed allow list), so a throwaway stdio test server never gets spawned at all. Anyone on an unrestricted account should be able to reproduce with any trivial stdio MCP server.

Expected behavior

When the MCP client is rebuilt after authentication, the previous generation of stdio child processes should be terminated and reaped — the same teardown that 1.0.51 added for session.disconnect().

Ideally the post-authentication reconfiguration would only (re)start the GitHub MCP server rather than recycling every configured server, since the other servers' configuration has not changed.

Additional context

Timeline from one real session (1.0.78), from --log-level debug:

17:16:11  spawn stdio server #1 (pid A); connect HTTP servers
17:17:02  stdio server #1 completes handshake ("Service initialized as client")
17:17:02  [ERROR] GitHub MCP server configured after authentication
17:17:03  → entire MCP client rebuilt:
             - spawn stdio server #2 (pid B)
             - HTTP servers: "worker quit with fatal: Transport channel closed"
             - "Handling tools refresh for <server>"
17:17:14  stdio server #2 completes handshake

pid A is never killed. It keeps running with its stdin pipe dangling.

Decisive evidence that the child is abandoned, not merely idle:

I killed the 6 suspected orphans across my running sessions. All 6 became Z+ (zombie) rather than disappearing:

$ kill 29835 56680 26768 92092 83359 43758
$ ps -p 29835 -o pid=,stat=
29835 Z+

A child only stays a zombie if its parent never called wait()/waitpid(). The CLI processes were still running and still owned these PIDs, but had no outstanding wait on them. After the kill, every session was left with exactly one live MCP child and all sessions kept working normally — confirming the killed ones were dropped, not in use.

Correlation check. Across 4 sessions I compared, the counts matched exactly every time:

occurrences of "GitHub MCP server configured after authentication"
  == number of "Service initialized as client" handshakes for that server
  == number of spawned child processes

Impact. 18 concurrent interactive sessions accumulated 6 orphans. Because this particular stdio server is heavyweight (~25 MB idle, ~570 MB warm), total MCP server residency reached ~3 GB, of which the orphans were pure waste. Long-lived terminal sessions make this monotonically worse — the oldest orphan had been running for 2 days 15 hours before I noticed.

Related issues:

Environment: macOS 26.6 (darwin-arm64), Node v24.18.1, zsh, Ghostty/iTerm2.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:mcpMCP server configuration, discovery, connectivity, OAuth, policy, and registryarea:sessionsSession management, resume, history, session picker, and session state

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions