github / github/copilot-cli

WSL2 (ARM64): `/copy` fails with `clip.exe exited with code 1` due to cmd.exe quoting in 1.0.55

未关闭
#3,534 6 条评论 5 个 reaction 已指派 0 人 在 GitHub 查看
area:input-keyboard area:platform-windows
主要语言
Shell
星标
11.2k
派生
1.9k
平均合并
14 小时 16 分钟
30 天内合并 PR
6

描述

# `/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.

贡献指南

打开贡献指南

评估

这个 Issue 还没有评估数据。

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。