[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

Resolved 💬 2 comments Opened Jun 9, 2026 by jtaddeo-mofin Closed Jun 13, 2026

Summary

On Claude Desktop (Windows) connecting to a WSL2 remote, two related failures:

  1. The remote launcher reports fork/exec /home/<user>/.claude/remote/ccd-cli/<version>: no such file or directory while 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.
  2. 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 + redeploy
  • chmod 700 / 755 adjustments on ~/.claude, ~/.claude/remote, ~/.claude/remote/ccd-cli
  • Wrapping the binary in a shell script that invokes the ELF loader explicitly
  • Putting node on PATH for non-interactive shells (this fixed a separate, pre-existing exec: "node": executable file not found issue, 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

  1. 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.
  2. 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 execve arguments 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.
  3. 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.
  4. 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.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗