WSL2 (ARM64): `/copy` fails with `clip.exe exited with code 1` due to cmd.exe quoting in 1.0.55
- Ngôn ngữ chính
- Shell
- Star
- 11.2k
- Fork
- 1.9k
- Merge trung bình
- 14 giờ 16 phút
- Pull request đã merge (30 ngày)
- 6
Mô tả
# `/copy` fails on WSL2 (ARM64): `clip.exe exited with code 1` — quoting bug in `cmd.exe` wrapper
## Summary
In Copilot CLI **1.0.55-1** running under WSL2 (Ubuntu, ARM64 / `aarch64`),
every clipboard write through the Windows path fails with:
```
Failed to copy to clipboard: Error: clip.exe exited with code 1
```
Triggered by `/copy`, "copy" affordances in the scroll view, `/session id`, and
anything else routed through `writeToClipboard()` → `writeToClipExe()`.
The bug is in **how the CLI quotes the `clip.exe` path inside the `cmd.exe`
wrapper**, not in `clip.exe`, not in WSL interop generally, and not in the
user's terminal/tmux setup. `clip.exe` itself works fine when invoked directly.
## Environment
- WSL: `WSL version 2.7.3.0`, `Kernel 6.6.114.1-1`, `WSLg 1.0.73`
- Distro: Ubuntu (`/home/driver/...`)
- Host: Windows 11 on ARM64
- Architecture: `aarch64` (CLI install dir: `~/.cache/copilot/pkg/linux-arm64/1.0.55-1/`)
- Terminal: Windows Terminal (`WT_SESSION` set) → inside `tmux 3.4`
- Copilot CLI: `1.0.55-1`
- Current working directory at time of failure: under `/home/driver/...`, which
translates to a UNC path `\\wsl.localhost\Ubuntu\home\driver\...`
> Note: CMD.EXE warns "UNC paths are not supported. Defaulting to Windows
> directory." on every invocation. That's noise — not the root cause. The
> wrapper still runs from `C:\Windows`, but the quoting bug breaks before
> `clip.exe` gets a chance.
## Root cause
In `app.js` (and the same code in `sdk/index.js`), `writeToClipExe()` builds
the command as:
```js
function mMs(t) {
let e = OU() ? fMs() : pMs(), // resolves to "C:\Windows\System32\clip.exe"
r = OU() ? "cmd.exe" : process.env.ComSpec || "cmd.exe";
return new Promise((n, o) => {
let s = _A(dMs(r, ["/d", "/c", `chcp 65001 >nul & "${e}"`]));
// ^^^^^^
// embedded double-quotes around the path
...
});
}
```
So the spawn args are effectively:
```
cmd.exe /d /c 'chcp 65001 >nul & "C:\Windows\System32\clip.exe"'
```
When Node's `child_process.spawn` ships that third argv element to Windows via
WSL interop, the embedded `"` characters get backslash-escaped (the standard
Win32 `CommandLineToArgvW` quoting rules applied by Node's spawn). By the time
CMD parses the `/c` string, it sees:
```
chcp 65001 >nul & \"C:\Windows\System32\clip.exe\"
```
CMD does not recognize `\"` as a quote escape, so it tries to execute a program
literally named `\"C:\Windows\System32\clip.exe\"`, which doesn't exist, and
exits with code 1. That bubbles up as the user-visible error.
## Reproduction
Inside WSL2 (Ubuntu, ARM64), with CWD anywhere under `/home/...`:
```bash
# Direct: works
echo "hello" | /mnt/c/Windows/System32/clip.exe
echo "exit=$?" # exit=0 ✓
# CMD wrapper without inner quotes: works
echo "hello" | /mnt/c/Windows/System32/cmd.exe /d /c \
'chcp 65001 >nul & C:\Windows\System32\clip.exe'
echo "exit=$?" # exit=0 ✓
# CMD wrapper WITH inner quotes (what the CLI does today): FAILS
echo "hello" | /mnt/c/Windows/System32/cmd.exe /d /c \
'chcp 65001 >nul & "C:\Windows\System32\clip.exe"'
echo "exit=$?"
# stderr: '"C:\Windows\System32\clip.exe"' is not recognized as an internal
# or external command, operable program or batch file.
# exit=1 ✗
```
Tested 100% reproducible on this box across:
- ASCII / empty / UTF-8 / 50 KB payloads — all fail identically
- With and without `chcp 65001 >nul &` prefix
- Direct `cmd.exe /d /c 'echo interop_works'` (no embedded quotes) succeeds, so
general interop is healthy
## Proposed fix
Drop the inner double-quotes — they're protecting against a path with spaces,
but `C:\Windows\System32\clip.exe` has none, and (more importantly) the System32
clip.exe path is the only thing this wrapper ever invokes:
```diff
- let s = _A(dMs(r, ["/d", "/c", `chcp 65001 >nul & "${e}"`]));
+ let s = _A(dMs(r, ["/d", "/c", `chcp 65001 >nul & ${e}`]));
```
(Source location: function `mMs()` in the minified `app.js`; same code is
duplicated 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 65001` prefix is a no-op for `clip.exe` anyway
since the binary is fed UTF-16-friendly text via stdin, and Node's `spawn`
with `windowsHide: true` already inherits the parent code page.
If the `chcp 65001` matters for some other reason I'm missing, an alternative
that still works around the quoting bug is to invoke via `start /b` or use a
single argv element with no nested quoting.
## Workaround for affected users
Patch the local install (will need to be re-applied after `/update`):
```bash
CLI_DIR=~/.cache/copilot/pkg/linux-arm64/1.0.55-1 # adjust for your version
for f in "$CLI_DIR/app.js" "$CLI_DIR/sdk/index.js"; do
cp "$f" "$f.bak-clipfix"
sed -i 's|chcp 65001 >nul & "${e}"|chcp 65001 >nul & ${e}|' "$f"
done
# then restart the CLI
```
## Notes / what this is NOT
- Not OSC 52: the CLI does fire OSC 52 first, but its "copied to clipboard" UI
message is gated on the `clip.exe` path succeeding (when present), and the
user sees the failure error before any OSC-52 fallback messaging.
- Not tmux: `tmux 3.4` with `set-clipboard external`/`off` will additionally
swallow 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'`).
- Not `clip.exe` shadowing: the changelog entry "Clipboard copy works correctly
on Windows when non-system clip.exe shadows the system one in PATH" suggests
the CLI already hardcodes the System32 path (`fMs()` / `pMs()`). That's
correct; the bug is purely the quoting around it.
- Not architecture-specific in principle, but I can only reproduce on
`linux-arm64`; an x86_64 WSL2 box may behave the same since the quoting
logic is identical — would appreciate confirmation from someone with an
AMD64 WSL2 setup.
Hướng dẫn đóng góp
Hướng nghiên cứu
Read writeToClipboard() and writeToClipExe() in app.js, then compare the duplicate implementation in sdk/index.js, focusing on mMs() and the cmd.exe spawn arguments. Run the supplied WSL2 reproduction, including direct clip.exe and the wrapped invocation, and verify that both CLI paths copy successfully with the existing error reporting intact.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- javascript, linux, node.js, ubuntu
- Lĩnh vực
- cli, operating-systems
- Loại issue
- Lỗi
- Độ khó
- 3/5
- Thời gian dự kiến
- 1-2 ngày
- Mức độ hoạt động
- Sôi nổi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức phù hợp với người mới
- 75/100