MCP servers report failure on /exit due to SIGKILL without graceful shutdown
Description
When executing /exit in Claude Code, all MCP servers are reported as failed, even though they were functioning correctly during the session.
Steps to Reproduce
- Configure any MCP server in
~/.claude.json:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
}
}
}
- Start Claude Code
- Use the MCP server (works correctly)
- Execute
/exit - Observe: "1 MCP server failed" message appears
Expected Behavior
MCP servers should shut down gracefully without reporting failure when exiting Claude Code normally.
Actual Behavior
All MCP servers report failure on /exit, regardless of implementation:
- Custom STDIO servers
- STDIO-to-SSE bridges
-
Official Anthropic servers (
@modelcontextprotocol/server-filesystem)
Root Cause Analysis
Through extensive debugging, we determined that Claude Code sends SIGKILL directly to MCP server processes without:
- Closing stdin first (as per MCP protocol spec)
- Sending
SIGTERMto allow graceful shutdown - Waiting for the process to exit naturally
Evidence:
- Added comprehensive signal handlers (
SIGTERM,SIGINT,SIGHUP) - Added stdin
end/close/errorevent listeners - Added parent PID watchdog
- Added
stdin.readablepolling - None of these handlers ever fire - the process is killed instantly
The MCP specification states:
"For the stdio transport, the client SHOULD send SIGTERM to the child process. The client can send SIGKILL to forcefully terminate if the child does not exit within a reasonable time."
Environment
- Claude Code version: 2.0.76
- macOS Darwin 25.2.0 (also reproduced on other systems)
- Node.js MCP servers
Impact
- Cosmetic issue (doesn't affect functionality)
- Confusing UX - users think their MCP servers are broken
- Prevents clean shutdown logging/cleanup in MCP servers
Workaround
None - the error can be safely ignored as it doesn't affect actual functionality.
🤖 Generated with Claude Code