Repository navigation
WSL2 (ARM64): /copy fails with clip.exe exited with code 1 due to cmd.exe quoting in 1.0.55 #3534
Description
Activity
Also worth mentioning that
/copyisn't the only problem - it also wasn't able to copy/paste anything out of the copilot CLI terminal via right-click or CTRL+C or CTRL+SHIFT+C until the patch was applied. I was unable to get anything to the clipboard from inside the copilot CLI while the bug was active.- addedarea:platform-windowsWindows-specific: PowerShell, cmd, Git Bash, WSL, Windows TerminalWindows-specific: PowerShell, cmd, Git Bash, WSL, Windows Terminalarea:input-keyboardKeyboard shortcuts, keybindings, copy/paste, clipboard, mouse, and text inputKeyboard shortcuts, keybindings, copy/paste, clipboard, mouse, and text input
on May 27, 2026 Confirming this on a second architecture and environment — same root cause, same fix.
Environment
- Copilot CLI 1.0.61 (also seen on 1.0.60), Node v24, linux-x64 (original report was arm64)
- WSL2 Ubuntu on a Windows 365 Cloud PC, accessed over RDP
- Reproduces both standalone and inside a wrapper that pipes stdout (non-TTY)
Confirmed root cause (unchanged from the original report)
In 1.0.61 the function is
Vcoinapp.js(duplicated insdk/index.js):function Vco(t){ let e = gV() ? Gco() : jco(), // e -> "C:\Windows\System32\clip.exe" r = gV() ? "cmd.exe" : process.env.ComSpec || "cmd.exe"; return new Promise((n,o)=>{ let s = Xc(zco(r,["/d","/c",`chcp 65001 >nul & "${e}"`])); // <-- inner quotes // ... }); }
The inner
"${e}"quotes get backslash-escaped by Node's Win32 spawn quoting as the argv crosses WSL interop, so cmd.exe receives\"C:\Windows\System32\clip.exe\", fails to find the command, and exits 1.Repro (x64, faithful to how the CLI spawns it)
# WITH inner quotes (what the CLI does) -> FAILS printf x | /mnt/c/Windows/System32/cmd.exe /d /c 'chcp 65001 >nul & "C:\Windows\System32\clip.exe"'; echo $? # '"C:\Windows\System32\clip.exe"' is not recognized ... -> 1 # WITHOUT inner quotes -> WORKS printf x | /mnt/c/Windows/System32/cmd.exe /d /c 'chcp 65001 >nul & C:\Windows\System32\clip.exe'; echo $? # -> 0
The one-line fix proposed in this issue (drop the inner quotes) is verified working on x64. I applied it to both
app.jsandsdk/index.jsand/copynow succeeds.Additional finding — why this is fatal in some setups (likely the same root cause behind #3521)
writeToClipboard(Zxe) only emits the OSC 52 fallback when stdout is a TTY:function qvr(){ return process.stdout.isTTY === true && process.env.TERM !== "dumb"; }
On
clip.exefailure, the error is swallowed only if the OSC 52 path was taken. When stdout is not a TTY (e.g. running under a wrapper that pipes stdout),qvr()is false, OSC 52 is skipped, and the clip.exe error is rethrown as a hard failure. So:- In a normal TTY terminal, users may see the error but content still lands via OSC 52 (/copy shows clipboard error but still copies successfully #3521).
- Under a non-TTY wrapper, the copy genuinely fails with no fallback.
Fixing the quoting resolves the hard-failure case; separately, gating the surfaced error on "all backends failed" (rather than showing the first backend's error) would resolve #3521.
Confirming on 1.0.65, linux-x64 (WSL2 Ubuntu, Windows 11, inside tmux 3.4) -- still reproduces, same root cause and same one-line fix. In 1.0.65 the function is now
o_rinapp.js:function o_r(t){ let e = Nk() ? r_r() : n_r(), // e -> "C:\Windows\System32\clip.exe" n = Nk() ? "cmd.exe" : process.env.ComSpec || "cmd.exe"; return new Promise((r,o)=>{ let s = ob(t_r(n,["/d","/c",`chcp 65001 >nul & "${e}"`])); // <-- inner quotes ...
Faithful repro (x64):
# WITH inner quotes (what the CLI does) -> FAILS, exit 1 printf x | /mnt/c/Windows/System32/cmd.exe /d /c 'chcp 65001 >nul & "C:\Windows\System32\clip.exe"'; echo $? # '"C:\Windows\System32\clip.exe"' is not recognized ... -> 1 # WITHOUT inner quotes -> WORKS, exit 0 printf x | /mnt/c/Windows/System32/cmd.exe /d /c 'chcp 65001 >nul & C:\Windows\System32\clip.exe'; echo $? # -> 0
I can also confirm the TTY/OSC-52 nuance from the earlier comment: it surfaced as a hard error here, consistent with the OSC-52 fallback not being taken (
acceptsOsc()gating onstdout.isTTY). tmux is not involved in the failure -- the quoting breaks before clip.exe runs, regardless of tmux clipboard settings.Alternative workaround that survives
/updateThe
sed-patch approach has to be re-applied after every update (the bundle lives in a version-pinned~/.cache/copilot/pkg/.../<version>/dir). Since the CLI spawns the bare name"cmd.exe"on WSL (resolved viaPATH), you can instead drop a smallcmd.exeshim earlier onPATH(e.g.~/.local/bin, ahead of/mnt/c/Windows/System32). It intercepts only the exact clipboard call and delegates everything else to the realcmd.exe, so it's transparent and never needs re-applying:cat > ~/.local/bin/cmd.exe <<'SH' #!/bin/bash REAL_CMD="/mnt/c/Windows/System32/cmd.exe" if [[ "$#" -eq 3 && "$1" == "/d" && "$2" == "/c" && "$3" == *clip.exe* && "$3" == *chcp* ]]; then payload="$(cat)" if command -v powershell.exe >/dev/null 2>&1; then printf '%s' "$payload" | powershell.exe -NoProfile -NonInteractive \ -Command '$in=[Console]::In.ReadToEnd(); Set-Clipboard -Value $in' && exit 0 fi printf '%s' "$payload" | /mnt/c/Windows/System32/clip.exe exit $? fi exec "$REAL_CMD" "$@" SH chmod +x ~/.local/bin/cmd.exe
(Using
Set-Clipboardhere also sidesteps the UTF-8/BOM corruption reported in #2571 / #3062. The real fix is still the one-line de-quote in the CLI -- this is just a cleaner stopgap than patching the bundle.)AI-ready quick win: still reproducible in 1.0.74-3
This remains unfixed in Copilot CLI 1.0.74-3 as of 2026-07-22. Every
/updatereinstalls the same broken expression, so affected WSL users must patch both generated bundles again. I just had to reapply the exact workaround toapp.jsandsdk/index.js.The functional fix remains one expression:
- `chcp 65001 >nul & "${e}"` + `chcp 65001 >nul & ${e}`
A cleaner source-level fix would spawn
clip.exedirectly and remove the unnecessarycmd.exequoting layer.Why this is an unusually strong quick-win / major quality-of-life fix:
- Clipboard access is a core CLI interaction, not an edge feature.
- The failure affects
/copyand terminal copy affordances, and can become a hard failure when OSC 52 is unavailable. - The root cause, faithful reproduction, exact fix, and cross-architecture confirmation are already documented here.
- The implementation is tiny, deterministic, and needs no product or design decision.
- A regression test can assert the generated argv or exercise the faithful WSL command shape.
AI-ready, one-cycle acceptance criteria:
- Fix the shared source that constructs the
clip.exeinvocation so nested quotes cannot be corrupted through WSL interop. - Add a regression test covering the failing quoted shape and successful corrected shape.
- Ensure both CLI and SDK build outputs inherit the source fix rather than patching generated bundles independently.
- Verify
linux-arm64andlinux-x64WSL paths.
Please consider labeling this good first issue, help wanted, or quick win, or assigning it directly to a Copilot coding agent. This is exactly the kind of tightly scoped standing bug that even the smallest available coding-agent tier should be able to resolve end-to-end in one cycle.
Confirming this remains reproducible in the current release, Copilot CLI 1.0.83, on WSL2 x86_64 as of September 10, 2026.
Environment
- Copilot CLI:
1.0.83 - Architecture:
linux-x64 - WSL distro: Ubuntu
- Host terminal: Alacritty on Windows
- Multiplexer: tmux 3.4
- Installation: Nix package from
numtide/llm-agents.nix
This is not caused by Nix sandboxing or tmux. The exact command constructed by the CLI fails when reproduced independently:
printf x | /mnt/c/Windows/System32/cmd.exe /d /c \ 'chcp 65001 >nul & "C:\Windows\System32\clip.exe"'
Output:
'\"C:\Windows\System32\clip.exe\"' is not recognized as an internal or external command, operable program or batch file.Exit status:
1.Both alternatives work:
# Invoke clip.exe directly. printf x | /mnt/c/Windows/System32/clip.exe # Or omit the nested quotes. printf x | /mnt/c/Windows/System32/cmd.exe /d /c \ 'chcp 65001 >nul & C:\Windows\System32\clip.exe'
The generated
app.jsin 1.0.83 still contains:spawn(cmd, ["/d", "/c", `chcp 65001 >nul & "${clip}"`])
Changing it to the unquoted form fixes
/copyand interactive selection copying. One packaging nuance: the Linux distribution includes a Node SEA executable with the JavaScript embedded in the binary, so modifying only the adjacentapp.jshas no effect unless the patched bundle is run through Node or the executable itself is rebuilt.The latest-release reproduction confirms the original one-line diagnosis and proposed fix are still valid on x86_64.
- Copilot CLI:
Confirming this remains reproducible in Copilot CLI 1.0.88 on WSL2 x86_64 with tmux 3.4.
cmd.exe /c versucceeds, so WSL interop is healthy. Both cached 1.0.88 bundles still contain:`chcp 65001 >nul & "${n}"`Patching both
app.jsandsdk/index.jsto:`chcp 65001 >nul & ${n}`and restarting Copilot fixes
/copy. The corrected command exits 0. Enabling tmuxset-clipboarddid not resolve theclip.exeexit-code failure, confirming that tmux/OSC 52 is separate from the underlying quoting bug.2026-10-07T09:57:07.194Z [WARNING] writeToClipboard: clip.exe failed; relied on OSC 52: Error: clip.exe exited with code 1
Not on WSL. This is just Cmd or PowerShell. Going on for many many months now. Please fix, Microsoft.
/copyfails on WSL2 (ARM64):clip.exe exited with code 1— quoting bug incmd.exewrapperSummary
In Copilot CLI 1.0.55-1 running under WSL2 (Ubuntu, ARM64 /
aarch64),every clipboard write through the Windows path fails with:
Triggered by
/copy, "copy" affordances in the scroll view,/session id, andanything else routed through
writeToClipboard()→writeToClipExe().The bug is in how the CLI quotes the
clip.exepath inside thecmd.exewrapper, not in
clip.exe, not in WSL interop generally, and not in theuser's terminal/tmux setup.
clip.exeitself works fine when invoked directly.Environment
WSL version 2.7.3.0,Kernel 6.6.114.1-1,WSLg 1.0.73/home/driver/...)aarch64(CLI install dir:~/.cache/copilot/pkg/linux-arm64/1.0.55-1/)WT_SESSIONset) → insidetmux 3.41.0.55-1/home/driver/..., whichtranslates to a UNC path
\\wsl.localhost\Ubuntu\home\driver\...Root cause
In
app.js(and the same code insdk/index.js),writeToClipExe()buildsthe command as:
So the spawn args are effectively:
When Node's
child_process.spawnships that third argv element to Windows viaWSL interop, the embedded
"characters get backslash-escaped (the standardWin32
CommandLineToArgvWquoting rules applied by Node's spawn). By the timeCMD parses the
/cstring, it sees:CMD does not recognize
\"as a quote escape, so it tries to execute a programliterally named
\"C:\Windows\System32\clip.exe\", which doesn't exist, andexits with code 1. That bubbles up as the user-visible error.
Reproduction
Inside WSL2 (Ubuntu, ARM64), with CWD anywhere under
/home/...:Tested 100% reproducible on this box across:
chcp 65001 >nul &prefixcmd.exe /d /c 'echo interop_works'(no embedded quotes) succeeds, sogeneral interop is healthy
Proposed fix
Drop the inner double-quotes — they're protecting against a path with spaces,
but
C:\Windows\System32\clip.exehas none, and (more importantly) the System32clip.exe path is the only thing this wrapper ever invokes:
(Source location: function
mMs()in the minifiedapp.js; same code isduplicated in
sdk/index.js.)If protection against future paths-with-spaces is desired, the correct
WSL-safe shape is to drop the wrapper entirely and
spawn('clip.exe', [])directly — that path also works in my testing and avoids the cmd.exe quoting
hazard altogether. The
chcp 65001prefix is a no-op forclip.exeanywaysince the binary is fed UTF-16-friendly text via stdin, and Node's
spawnwith
windowsHide: truealready inherits the parent code page.If the
chcp 65001matters for some other reason I'm missing, an alternativethat still works around the quoting bug is to invoke via
start /bor use asingle argv element with no nested quoting.
Workaround for affected users
Patch the local install (will need to be re-applied after
/update):Notes / what this is NOT
message is gated on the
clip.exepath succeeding (when present), and theuser sees the failure error before any OSC-52 fallback messaging.
tmux 3.4withset-clipboard external/offwill additionallyswallow OSC 52, but that's a separate (and well-known) issue with its own
fix (
set -g set-clipboard on; set -g allow-passthrough on; set -as terminal-features ',*:clipboard').clip.exeshadowing: the changelog entry "Clipboard copy works correctlyon Windows when non-system clip.exe shadows the system one in PATH" suggests
the CLI already hardcodes the System32 path (
fMs()/pMs()). That'scorrect; the bug is purely the quoting around it.
linux-arm64; an x86_64 WSL2 box may behave the same since the quotinglogic is identical — would appreciate confirmation from someone with an
AMD64 WSL2 setup.