WSL remote: CLI download livelocks on slow connections (180s idle timeout, no fetch progress, concurrent installs delete each other's temp files)
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?
Opening a WSL project from Claude Desktop (Windows, MSIX build) hangs on "Starting session" and fails every time with:
SSH connection setup timed out during "deploying: Downloading Claude Code CLI..." (idle 180003ms)
The remote server binary (claude-ssh, version 7d193f89fc02cf1035a391245312e34ad419f63e) uploads fine via SFTP. The failure is in the next step, where the remote server fetches the Claude Code CLI .zst (https://downloads.claude.ai/claude-code-releases/2.1.246/linux-x64/claude.zst, 78,105,392 bytes) into ~/.claude/remote/ccd-cli/. Three interacting behaviours make this step impossible to complete on a slow link:
- The remote fetch reports no progress to the client. Server uploads emit
SFTP upload progresslines every ~1MB, but during--install --cli-url ...nothing is emitted until the download finishes. The client's last-progress marker stays at the server upload (last progress: 7127224/7127224B).
- The client aborts after 180s of "idle". Because (1) produces no output, any CLI download needing more than 180s is killed, even though it is progressing normally.
- Concurrent installs delete each other's
.fetch-*temp files. The client runs overlapping setups (trigger: warm_upandtrigger: start_sessionstart within the same second), and aborted installs leave orphaned fetches running in WSL. The install path's prune/sweep (install.pruneCliDir/install.sweepOrphans) removes the other run's in-progress temp file, so even a fetch that finishes under 180s dies:
[BinaryDeployment] Remote CLI fetch failed, falling back to SFTP: chmod: chmod /home/ayman/.claude/remote/ccd-cli/.fetch-1089879865: no such file or directory
The whole ~/.claude/remote/ tree is also wiped between attempts (remote=missing on the next setup), so the server is re-uploaded and the cycle restarts from zero.
Measured numbers
| Measurement | Value |
|---|---|
| CLI artifact size (claude-code-releases/2.1.246/linux-x64/claude.zst) | 78,105,392 bytes |
| Sustained download speed, WSL → downloads.claude.ai | ~284 KB/s |
| Sustained download speed, Windows host → downloads.claude.ai (same file, full 78MB) | ~313 KB/s |
| Time required at 284 KB/s | ~275 s |
| Client idle timeout | 180 s |
| Minimum speed needed to finish inside the timeout | ~450 KB/s (78 MB / 180 s) |
The WSL remote path is effectively unusable for anyone whose connection sustains less than ~450 KB/s to downloads.claude.ai. A fixed wall-clock timeout on a ~78MB download is the wrong tool — the timeout should apply to stalled transfers, which requires progress reporting from the remote fetch.
What Should Happen?
Opening a WSL project should start a session. The CLI download step should either complete or fail with a clear, actionable error.
Specifically:
- The remote fetch should stream progress (bytes received / total) the way SFTP uploads already do.
- The idle timeout should measure stalled transfers, not wall-clock time since the last message. A download progressing normally at 284 KB/s should not be killed.
- Concurrent setups (
warm_up+start_session) should be serialized per target, and an aborted install should terminate its remote fetch rather than leaving it orphaned. pruneCliDir/sweepOrphansshould not delete.fetch-*files belonging to an in-flight download.- Ideally, the remote server should reuse a compatible CLI already installed on the machine (e.g.
~/.local/share/claude/versions/<version>) when the version matches.
Error Messages/Logs
From `%LOCALAPPDATA%\Claude\Logs\ssh.log`:
15:36:57 [info] [BinaryDeployment] Deploying server binary linux-amd64 to /home/ayman/.claude/remote/srv/7d193f89... (remote=missing, ...)
15:36:59 [info] [BinaryDeployment] SFTP upload progress: .../server 7127224/7127224B (5.41 MB/s, 1256ms elapsed)
15:40:00 [warn] [BinaryDeployment] Aborting setup on wsl:Ubuntu
15:40:00 [error] [RemoteServerController] Connection failed (183900ms, trigger: warm_up, attempt 1): SSH connection setup timed out during "deploying: Downloading Claude Code CLI..." (idle 180003ms; last progress: 7127224/7127224B)
15:40:00 [info] [RemoteServerController] Ensuring server is ready on wsl:Ubuntu (trigger: start_session)
15:40:00 [warn] [BinaryDeployment] server --install produced no result; stderr (no detail)
15:43:00 [error] [RemoteServerController] Connection failed (180544ms, trigger: start_session, attempt 1): SSH connection setup timed out during "deploying: Downloading Claude Code CLI..." (idle 180002ms)
15:48:13 [warn] [BinaryDeployment] Remote CLI fetch failed, falling back to SFTP: chmod: chmod /home/ayman/.claude/remote/ccd-cli/.fetch-1089879865: no such file or directory
The same abort/retry pair repeated ~10 times over 90 minutes with identical timings.
Running the install step manually returns success with an empty result, which is the expected first-run answer (the pinned version simply isn't in the managed dir yet):
$ ~/.claude/remote/srv/*/server --install
__INSTALL_RESULT__{"serverVersion":"7d193f89...","os":"linux","arch":"amd64","libc":"glibc","cliPath":"","cliWasPresent":false}
exit code: 0
Steps to Reproduce
- Windows 11 + WSL2 (Ubuntu), on a connection that sustains under ~450 KB/s to
downloads.claude.ai. - Open Claude Desktop and add a project folder located inside WSL (e.g.
/home/<user>/projects/<project>). - Open that project. The app shows "Starting session".
- After ~180s it fails with:
SSH connection setup timed out during "deploying: Downloading Claude Code CLI..." (idle 180003ms). - Retries repeat indefinitely with identical timing.
~/.claude/remote/is wiped between attempts, so each cycle restarts from zero.
To confirm the cause, measure throughput to the artifact from inside WSL:
curl -o /dev/null -w 'avg %{speed_download} B/s, total %{time_total}s\n' \
https://downloads.claude.ai/claude-code-releases/2.1.246/linux-x64/claude.zst
Anything sustaining under ~450 KB/s cannot finish 78,105,392 bytes inside the 180s window.
To confirm nothing is wrong with the environment itself (all verified here):
- Server binary is
-rwxr-xr-xand runs:~/.claude/remote/srv/*/server --versionresponds correctly - Root filesystem is
ext4 rw, nonoexec - 951GB free disk
env -i /bin/sh -c 'command -v claude'resolves the locally installed CLI
Claude Model
Opus
Is this a regression?
Yes, this worked in a previous version
Last Working Version
N/A
Claude Code Version
2.1.250 (Claude Code) — locally installed in WSL. Desktop app pins CLI 2.1.246; remote server claude-ssh 7d193f89fc02cf1035a391245312e34ad419f63e (built 2026-08-25).
Platform
Other
Operating System
Windows
Terminal/Shell
PowerShell
Additional Information
Note on repo: this affects Claude Desktop's WSL remote deployment rather than the Claude Code CLI itself. Filing here since this is the closest public tracker — happy to move it if there's a better place.
Workaround that works
Pre-seed the exact pinned version so --install reports cliWasPresent:true and the download is skipped entirely:
mkdir -p ~/.claude/remote/ccd-cli
curl -L -o /tmp/claude.zst https://downloads.claude.ai/claude-code-releases/2.1.246/linux-x64/claude.zst
unzstd -o ~/.claude/remote/ccd-cli/2.1.246 /tmp/claude.zst
chmod +x ~/.claude/remote/ccd-cli/2.1.246
Verified: server --install --cli-dir ~/.claude/remote/ccd-cli --cli-version 2.1.246 then returns{"cliPath":"/home/ayman/.claude/remote/ccd-cli/2.1.246","cliWasPresent":true}, and the session opens immediately.
This has to be repeated every time the desktop app pins a new CLI version.
Suggested fixes
- Stream download progress from the remote fetch (bytes received / total) the way SFTP uploads already do, and make the idle timeout measure stalled progress rather than wall-clock since the last message.
- Serialize setups per target (don't start
start_sessionwhilewarm_upis still deploying), and have an aborted install terminate its remote fetch. - Make
pruneCliDir/sweepOrphansignore.fetch-*files younger than a few minutes, or lock the directory, so concurrent runs can't delete an in-flight download. - Prefer the SFTP fallback (which has progress reporting) up front when a previous remote fetch timed out, instead of retrying the same remote fetch.
- Optionally, reuse a compatible CLI already installed on the machine (e.g.
~/.local/share/claude/versions/<version>) when the version matches.