Windows OpenSSH + WSL: Large or Quoted Commands Fail Over SSH¶
TL;DR¶
On a Windows host whose OpenSSH DefaultShell routes into WSL through a cmd.exe
wrapper (C:\wsl-shell.cmd — see the DefaultShell wrapper article),
two things silently break remote commands run as ssh host "<command>":
- Embedded double quotes in the command corrupt the wrapper's
if … gotodispatch, and the command dies with" goto sftp was unexpected at this time. - cmd.exe caps the command line at ~8191 characters. A longer command is
silently truncated — the tail (often a
> destfileredirect) just vanishes, so the command "succeeds" but writes nothing and prints no error.
The reliable fix is to send a payload that contains no quotes and stays short:
pipe a script over stdin, or base64-encode it (gzip first when writing a
file). Verify file writes with sha256sum on both ends.
This was hit on MajorRig (Windows 11 + WSL2 Fedora) while driving the
majorvault-graph builder over SSH from MajorAir.
Symptoms¶
ssh majorrig 'cd ~/repo && echo "--- x ---"; git status'→" goto sftp was unexpected at this time.and nothing runs. Any remote command containing"triggers it.- A larger command — e.g.
echo <11KB-of-base64> | base64 -d > ~/dest— returns cleanly, exit 0, but the file is absent. No error on stdout or stderr. The> ~/desttail was chopped off past the ~8191-char limit, sobase64 -ddecoded to nowhere.
Cause¶
The DefaultShell wrapper runs remote commands as roughly:
%~2 is the command string sshd hands the wrapper. Two cmd.exe behaviors bite:
- Quote parsing at parse-time. The wrapper has earlier
if /I "%~2"=="…" goto sftpdispatch lines (they route the sftp subsystem). cmd expands%~2before syntax analysis, so a"inside your command closes theif's string early and cmd sees a straygoto→goto sftp was unexpected at this time. - The ~8191-char command-line ceiling. cmd.exe truncates anything longer. Because the truncation happens mid-string, a trailing redirect or pipe is lost and the command does something other than intended — usually nothing visible.
Fix¶
Send payloads that carry no quotes and stay under the length ceiling.
1. Run a script via stdin (simplest for multi-line work)¶
bash -s reads the script from stdin, which is not subject to the command-line
length limit and needs no quoting on the remote side.
2. Base64-encode the command (quote-safe)¶
Base64 output is only [A-Za-z0-9+/=] — no quotes to break the wrapper:
SCRIPT='cd ~/repo && echo "--- branch ---" && git status --short'
B64=$(printf '%s' "$SCRIPT" | base64 | tr -d '\n')
ssh majorrig "echo $B64 | base64 -d | bash"
The transmitted command is echo <b64> | base64 -d | bash — no quotes reach the
wrapper; the quotes live safely inside the base64 blob and are reconstituted by
bash on the WSL side.
3. Write a file with gzip + base64 (stays under the length limit)¶
For anything more than a few KB, gzip first so the base64 blob fits under ~8191 chars:
GZ=$(gzip -c localfile | base64 | tr -d '\n')
ssh majorrig "echo $GZ | base64 -d | gzip -d > ~/destfile"
# verify — do not trust a silent success
LOCAL=$(shasum -a 256 localfile | awk '{print $1}')
ssh majorrig 'sha256sum ~/destfile' | grep -q "$LOCAL" && echo OK || echo MISMATCH
An ~8 KB source file base64s to ~11 KB (over the limit → truncated write); gzipped
first it drops to ~3–5 KB and fits in one shot. For genuinely large files, either
split the base64 into >>-appended chunks or use scp (see caveat below).
Key notes¶
- Always verify file writes on this transport with
sha256sum. A truncated write is silent — exit 0, no output — so "it ran" is not proof "it wrote." scp/sftpland on the Windows filesystem (C:\Users\<user>), not the WSL home. To land a file in WSL,scpit to Windows and pick it up from/mnt/c/..., or use the base64 method above which writes directly inside WSL.- Prefer stdin (
bash -s) for running scripts and gzip+base64 for writing files. Reserve inlinessh host "…"for short, quote-free commands.
Related¶
- Windows OpenSSH + WSL: sftp Hangs — Subsystems Launch Through the DefaultShell — the wrapper this article's failures come from
- Windows OpenSSH: WSL Default Shell Breaks Remote Commands — why WSL is the DefaultShell in the first place
- Windows OpenSSH Server (sshd) Stops After Reboot