[BUG] Windows: Remote-control CLI process becomes zombie with CloseWait TCP socket after server-side idle timeout
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
When a Claude Code remote-control session (launched from the Claude Desktop app) goes idle for ~20+ minutes on Windows, the server-side TTL expires and closes the connection. However, the CLI process (claude.exe) does not detect the dead connection and continues running indefinitely as a zombie process.
Observed behavior:
- The CLI process remained alive for 20+ hours after the connection died
- TCP connection to Anthropic API (
2607:6bc0::10:443) stuck inCloseWaitstate — the server closed its end, but the client never acknowledged - Process consumed 1.07 GB private memory while completely idle (only 2 min 18s CPU time over 20 hours)
- 3 MCP server ports remained bound serving nothing
- The session appeared active in the desktop app but was completely unresponsive
- No automatic recovery or graceful exit occurred
Diagnostic evidence (PowerShell):
Get-NetTCPConnection -OwningProcess <PID> | Select LocalAddress, LocalPort, RemoteAddress, RemotePort, State
LocalAddress LocalPort RemoteAddress RemotePort State
:: 50151 :: 0 Bound
:: 50149 :: 0 Bound
:: 50147 :: 0 Bound
2605:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx 50147 2607:6bc0::10 443 CloseWait
Related: This is the Windows-side manifestation of #32982 (server-side TTL ignores keepalives). While #32982 focuses on the server-side root cause, this issue highlights a separate client-side bug: the CLI process should detect the dead connection (via CloseWait state or read timeout) and either reconnect or exit gracefully, rather than sitting as a zombie consuming 1GB+ RAM indefinitely.
What Should Happen?
When the server-side TTL expires and the API connection is closed, the CLI process should:
- Detect the dead connection within a reasonable timeout (e.g., 30-60 seconds) via TCP read errors or CloseWait detection
- Attempt to reconnect to the remote-control bridge automatically
- If reconnection fails, exit gracefully and release all resources (memory, bound ports, MCP server sockets)
- Surface the disconnection to the desktop app UI so the user knows the session is dead, rather than showing it as still active
The CLI process should never remain running indefinitely as a zombie consuming 1GB+ of RAM with no active connections.
Error Messages/Logs
Steps to Reproduce
- Open Claude Desktop app (v1.1.9493) on Windows 11
- Start a remote-control session (a
claude.exe --output-format stream-jsonprocess is spawned) - Use the session briefly, then leave it idle for 20+ minutes
- Attempt to send a message via the mobile app or desktop — it fails silently or shows "Failed to send message"
- On the Windows machine, check the CLI process:
tasklist | findstr claude— zombie CLI process still runningGet-NetTCPConnection -OwningProcess <PID>— shows CloseWait connection to Anthropic API- Process memory continues to be held (1GB+) indefinitely
- The only fix is manually killing the process:
taskkill /PID <PID> /F
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.87
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
PowerShell
Additional Information
Environment:
- Claude Desktop: v1.1.9493 (Windows Store)
- Claude Code CLI: v2.1.87
- OS: Windows 11 Pro 10.0.26200
- Hardware: Lenovo Yoga 9i
- Plugins loaded: Slack, Telegram
Suggested client-side fixes:
- Add a TCP-level read timeout or periodic health check on the API WebSocket connection
- Detect CloseWait state and trigger reconnection or graceful shutdown
- Implement an idle watchdog that exits the CLI if no API activity for N minutes and reconnection fails
- Surface connection state to the desktop app so it can show "Disconnected" instead of appearing active
Workaround: Periodically run a scheduled task to detect and kill zombie sessions:
Get-NetTCPConnection -State CloseWait |
Where-Object { (Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).Path -match 'claude-code' } |
ForEach-Object { Stop-Process -Id $_.OwningProcess -Force }This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗