[BUG] WSL remote on Windows: false `fork/exec ... no such file or directory` on ccd-cli (binary is present); old chat windows can't reconnect after remote-server restart
Summary
On Claude Desktop (Windows) connecting to a WSL2 remote, two related failures:
- The remote launcher reports
fork/exec /home/<user>/.claude/remote/ccd-cli/<version>: no such file or directorywhile the binary is present, executable, and directly runnable. The user-facing popup says "The Claude Code binary on the remote host is missing. Send your message again to redeploy it." — but resending does nothing; the binary was never actually missing. - After the WSL-side remote-server restarts (for any reason — auto-upgrade,
wsl --shutdown, etc.), already-open Desktop chat windows cannot reconnect. They appear to hold a dead socket FD with no recovery path. Only freshly-opened chats find the new socket. Conversation history is preserved on disk; only the live client binding is lost.
Also captured in passing — a separate path-translation bug that may share a code path with #1, evidence below: Claude Desktop passes a Windows-style extension path verbatim into the WSL node invocation for one of its built-in MCP extensions.
Environment
- Claude Desktop on Windows, connecting to WSL2 remote (Ubuntu,
Linux 6.6.87.2-microsoft-standard-WSL2 x86_64) - Remote helper:
ccd-cli/2.1.165, dynamically-linked ELF, deployed at~/.claude/remote/ccd-cli/2.1.165 - Remote-server (Go binary) at
~/.claude/remote/srv/<hash>/server, listening on Unix socket at~/.claude/remote/run/<id>/rpc.sock - User: plain alphanumeric (
joet), no spaces — not the username-with-space variant - Architecture: x86_64; plenty of free disk; standard permissions
Issue 1 — False ENOENT on ccd-cli launch
The Desktop popup:
Couldn't start Claude Code on the remote host
The Claude Code binary on the remote host is missing. Send your message again to redeploy it.
Claude Code process exited with code 1
[spawn] fork/exec /home/<user>/.claude/remote/ccd-cli/2.1.165: no such file or directory
State on the WSL side at the time of failure, directly verified:
$ ls -la ~/.claude/remote/ccd-cli/
-rwxr-xr-x 1 joet joet 244917968 Jun 9 12:43 2.1.165
$ file ~/.claude/remote/ccd-cli/2.1.165
ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked,
interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, not stripped
$ ~/.claude/remote/ccd-cli/2.1.165 --version
2.1.165 (Claude Code)
So the binary exists, has the right shape, has the expected interpreter, and runs cleanly when invoked directly from a normal shell. The user's git binary launches fine from the remote-server in the same window, so the ELF loader is not broken in general. Only ccd-cli gets the false ENOENT, and only from the remote-server's spawn — direct invocation succeeds.
The recommended user action ("Send your message again to redeploy it") is wrong here and actively misleading — the binary isn't missing, so nothing gets redeployed. This sent us down hours of binary-debugging that wasn't actually about the binary.
Issue 2 — Stale-socket: old chat windows can't reconnect after remote-server restart
After a full reset (wsl --shutdown from PowerShell, then mv ~/.claude/remote ~/.claude/remote.dead.$(date +%s) from WSL, then reopen Desktop), Issue 1 cleared and new chats work. Existing chat windows that were open before the reset still cannot connect.
What I observed on disk afterward:
~/.claude/remote.broken.20260609120053/— first auto-quarantine (12:00 today)~/.claude/remote.dead.1781023341/— second quarantine (12:28)~/.claude/remote/— fresh, rebuilt at 12:43
Each quarantine has its own run/<id>/rpc.sock from when it was live. After rotation the nominal socket path looks the same to the client, but the underlying socket file is a new inode in a different directory tree — the old client's FD points at a deleted socket.
$ ps aux | grep claude
# multiple live windows with --resume <session-id> connecting to the new socket
# old windows from before the rotation: their CCD CLI process is gone or wedged
Symptom from the user side: "only new chats work, old chats won't connect." Closing the dead chat and re-opening a window with the same session ID (--resume) works — the conversation transcripts in ~/.claude/projects/<project>/ are intact. But the UX is brittle: it requires understanding that the old window is unrecoverable and knowing how to resume by session ID. Most users will just lose work.
Ideal fix would be client-side: on socket disconnect, retry against the current ~/.claude/remote/run/<id>/rpc.sock instead of giving up.
Adjacent finding — Windows path leaks into WSL node invocation (separate bug, related evidence)
While diagnosing Issue 1 we instrumented node to log every invocation. Captured this call from Claude Desktop:
---- Tue Jun 9 12:20:45 EDT 2026 ----
argv: node C:\Users\JosephTaddeo\AppData\Roaming\Claude\Claude Extensions\ant.dir.gh.anthropic.pdf-server-mcp/dist/index.js --stdio
PATH=/home/joet/.local/bin:/home/joet/.local/share/mise/installs/node/...
PWD=/home/joet
The Linux node then tried to resolve a Windows path as relative-to-CWD:
Error: Cannot find module '/home/joet/C:\Users\JosephTaddeo\AppData\Roaming\Claude\Claude Extensions\ant.dir.gh.anthropic.pdf-server-mcp/dist/index.js'
code: 'MODULE_NOT_FOUND',
This is the PDF MCP extension (ant.dir.gh.anthropic.pdf-server-mcp), shipped with Claude Desktop. Its extension path stored on Windows (C:\Users\...AppData\Roaming\Claude\Claude Extensions\...) is being passed verbatim to the WSL node binary instead of being translated to /mnt/c/Users/... or resolved to a WSL-side install.
Uninstalling the PDF extension removed this specific log line. Issue 1 persisted, so the PDF MCP isn't the direct cause of Issue 1, but the existence of one Windows→WSL path-translation bug suggests Issue 1 could be a sibling — the launcher's reported path (/home/<user>/.claude/remote/ccd-cli/2.1.165) might be a display artifact and the actual exec target might be something else (e.g., the Windows-side equivalent path) that genuinely doesn't exist.
What didn't help (so other users don't burn time here)
The user-facing error message and online advice both push toward binary/cache repair. None of the following resolved Issue 1:
rm -rf ~/.claude/remote/ccd-cli+ redeploy (the redeploy succeeded, binary returned, error returned)rm -rf ~/.claude/remote+ redeploychmod 700 / 755adjustments on~/.claude,~/.claude/remote,~/.claude/remote/ccd-cli- Wrapping the binary in a shell script that invokes the ELF loader explicitly
- Putting
nodeon PATH for non-interactive shells (this fixed a separate, pre-existingexec: "node": executable file not foundissue, but did not fix Issue 1) pkill -f '.claude/remote'+ restart in-place
What actually cleared Issue 1: wsl --shutdown from PowerShell + move ~/.claude/remote aside + fresh Desktop connect. The state that needed clearing wasn't the binary; it was the running remote-server / its launcher chain.
Suggested fixes / next steps
- Fix the error message. When the launcher fails to exec the remote binary, the message should not unconditionally claim the binary is missing. At minimum: stat the path before reporting "missing" vs "exists but exec failed", and surface the underlying errno. The current message sends users on a fruitless cache-wipe spiral.
- Investigate the false ENOENT. Direct exec works; remote-server spawn fails. The remote-server is a Go binary; reproducing should be a matter of capturing the
execvearguments it passes vs the path it logs. Strong hypothesis given the adjacent path-leak finding: the launcher constructs the exec path on the Windows side and passes it across, with subtle escaping/translation bugs that aren't visible in the error message. - Stale-socket recovery in the client. When the WSL-side remote-server restarts, existing chat windows should retry against the current socket instead of failing permanently.
- Path translation for Desktop-shipped MCP extensions. The PDF MCP path being passed Windows-style into WSL is a clear bug; presumably affects any Desktop extension a user enables when connecting to a WSL remote.
Happy to provide additional logs from ~/.claude/remote.broken.* and ~/.claude/remote.dead.* if useful — they're still on disk.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗