Bug Report: /exit hangs when MCP servers are connected — processes not cleaned up
Environment
- CommandCode version:
0.25.12
- OS: macOS (Darwin, ARM64)
- Node.js: Bundled with npm install
- Installation:
npm install -g command-code
Description
When MCP servers are configured and active, the /exit command does not properly disconnect or terminate the underlying MCP child processes (stdio transports spawned via child_process.spawn() or HTTP transports). This leaves the CommandCode process hanging indefinitely, requiring the user to send Ctrl+C (SIGINT) or kill -9 from another terminal to regain shell control.
Steps to Reproduce
- Configure one or more MCP servers (e.g., stdio-based via
npx, or HTTP-based).
- Launch
commandcode (or cmd).
- Verify MCP servers are connected using
/mcp.
- Type
/exit and press Enter.
- Expected: CommandCode cleanly disconnects all MCP transports, terminates child processes, and returns to the shell prompt.
- Actual: CommandCode displays the logo/session end and hangs. The terminal cursor blinks but never returns to the shell prompt.
Evidence
Process tree before /exit
$ ps aux | grep -i mcp
angelsalazar 38172 0.0 0.1 1234567 2345 s000 S+ 3:42PM 0:02.45 node /.../mcp-server/index.js
After /exit — CommandCode hangs
# Command Code v0.25.12
# models: kimi-k2.6 · taste-1
# /Volumes/SSDWD2T/gic-projects/PRESTAMO-VEHICULAR
/exit
[HANGS — cursor blinking, no shell prompt]
After forced Ctrl+C — orphaned MCP still alive
$ ps aux | grep 38172
angelsalazar 38172 0.0 0.1 1234567 2345 s000 S 3:42PM 0:02.45 node /.../mcp-server/index.js
The MCP child process (PID 38172) remains alive even after CommandCode is forcefully interrupted.
Root Cause Analysis (from reverse-engineered dist/index.mjs)
In the command handler for /exit (found at approximately line 2xx in the bundled output):
if("/exit"===s) return t.setShouldExit(!0),{status:"handled"}
This sets a React state flag shouldExit but does not invoke any cleanup routine for active MCP connections before returning. The McpConnectionManager (which tracks StdioTransport and HttpTransport instances) has a close() method available, but it is never called during the exit flow.
Relevant code patterns found in the bundle:
StdioTransport (src/mcp/client/stdio-transport.ts):
async close() {
this.process && (this.process.stdin?.end(), this.process.kill(), this.process = null);
this.isConnected = false;
this.pendingRequests.clear();
}
HttpTransport (src/mcp/client/http-transport.ts):
// Uses AbortController; connection cleanup relies on transport.close()
Neither transport's close() is invoked when setShouldExit(true) fires. The stdio child processes (node, npx, python, etc.) are therefore never signaled to terminate, keeping the Node.js event loop alive and preventing clean process exit.
Impact
- User Experience: Shell becomes unresponsive after every session end when MCPs are active.
- Resource Leak: Orphaned Node.js/Python processes accumulate in the background.
- Workaround Required: Users must wrap
commandcode in a custom shell script that force-kills MCP processes after exit (see workaround below).
Workaround (for users until fixed)
Create a wrapper script that snapshots MCP processes before launch and kills orphans after exit:
#!/bin/bash
BEFORE=$(ps -eo pid,args | grep -i mcp | grep -v grep | awk '{print $1}')
commandcode "$@"
# After exit, kill any new MCP processes
for pid in $(ps -eo pid,args | grep -i mcp | grep -v grep | awk '{print $1}'); do
[[ -n "$pid" ]] && ! echo "$BEFORE" | grep -q "^${pid}$" && kill -9 "$pid" 2>/dev/null
done
Suggested Fix
In the /exit handler (or in the React component's exit effect), iterate through McpConnectionManager.getConnectedServers() and call disconnect()/close() on each transport before allowing the process to terminate:
// Pseudocode for the exit flow
async function handleExit() {
const mcpManager = getMcpConnectionManager();
for (const server of mcpManager.getConnectedServers()) {
if (server.transport?.close) {
await server.transport.close();
}
}
setShouldExit(true);
}
Alternatively, register an atexit/process.on('exit') handler in McpConnectionManager itself to ensure cleanup happens regardless of how the CLI shuts down.
Related Issues
- None found — searched GitHub issues for
mcp hang, exit freeze, process stuck, and mcp cleanup with no matching results. This appears to be a new, unreported bug.
Additional Context
The issue was discovered while comparing CommandCode behavior with OpenCode (another AI coding agent). OpenCode cleanly disconnects MCP servers before exit, while CommandCode v0.25.12 consistently reproduces the hang.
Labels: bug, mcp, process-management, macOS
Bug Report:
/exithangs when MCP servers are connected — processes not cleaned upEnvironment
0.25.12npm install -g command-codeDescription
When MCP servers are configured and active, the
/exitcommand does not properly disconnect or terminate the underlying MCP child processes (stdio transports spawned viachild_process.spawn()or HTTP transports). This leaves the CommandCode process hanging indefinitely, requiring the user to sendCtrl+C(SIGINT) orkill -9from another terminal to regain shell control.Steps to Reproduce
npx, or HTTP-based).commandcode(orcmd)./mcp./exitand press Enter.Evidence
Process tree before
/exitAfter
/exit— CommandCode hangsAfter forced
Ctrl+C— orphaned MCP still aliveThe MCP child process (PID 38172) remains alive even after CommandCode is forcefully interrupted.
Root Cause Analysis (from reverse-engineered
dist/index.mjs)In the command handler for
/exit(found at approximately line 2xx in the bundled output):This sets a React state flag
shouldExitbut does not invoke any cleanup routine for active MCP connections before returning. TheMcpConnectionManager(which tracksStdioTransportandHttpTransportinstances) has aclose()method available, but it is never called during the exit flow.Relevant code patterns found in the bundle:
StdioTransport (
src/mcp/client/stdio-transport.ts):HttpTransport (
src/mcp/client/http-transport.ts):// Uses AbortController; connection cleanup relies on transport.close()Neither transport's
close()is invoked whensetShouldExit(true)fires. The stdio child processes (node,npx,python, etc.) are therefore never signaled to terminate, keeping the Node.js event loop alive and preventing clean process exit.Impact
commandcodein a custom shell script that force-kills MCP processes after exit (see workaround below).Workaround (for users until fixed)
Create a wrapper script that snapshots MCP processes before launch and kills orphans after exit:
Suggested Fix
In the
/exithandler (or in the React component's exit effect), iterate throughMcpConnectionManager.getConnectedServers()and calldisconnect()/close()on each transport before allowing the process to terminate:Alternatively, register an
atexit/process.on('exit')handler inMcpConnectionManageritself to ensure cleanup happens regardless of how the CLI shuts down.Related Issues
mcp hang,exit freeze,process stuck, andmcp cleanupwith no matching results. This appears to be a new, unreported bug.Additional Context
The issue was discovered while comparing CommandCode behavior with OpenCode (another AI coding agent). OpenCode cleanly disconnects MCP servers before exit, while CommandCode v0.25.12 consistently reproduces the hang.
Labels:
bug,mcp,process-management,macOS