Summary
/copy fails in every SSH / remote / container session with "Could not reach the
clipboard — is this a headless terminal?", because it writes to the OS clipboard of
the machine the CLI runs on rather than sending the text through the terminal.
This is a daily papercut for remote workflows. Copying agent output is a core loop in
a CLI agent, and today it silently degrades to manual terminal selection over SSH.
Notably, OpenCode handles this correctly using OSC 52 (text travels through the
terminal to the client), so ctrl+y works there in the exact same setup.
Expected Behavior
/copy should work in remote sessions. Two acceptable designs, in order of
preference:
-
OSC 52 — emit ESC ] 52 ; c ; <base64> BEL when no local clipboard is
available. The terminal emulator (VS Code, iTerm2, GNOME Terminal) intercepts it
and writes to the client's clipboard. This is the standard mechanism for remote
CLI clipboard and needs no dependency.
-
Reuse the existing terminal-escape path that /terminal-setup already sets up for
paste, so copy and paste use one consistent mechanism
Actual Behavior
/copy reports "Could not reach the clipboard — is this a headless terminal?" and
copies nothing.
Traced from the shipped bundle (dist/cli.mjs, minified — so function names are
accurate but there are no usable line numbers):
/copy → handleCopy() → writeClipboardText(), which first calls
isClipboardAvailable(). On Linux that reduces to isLinuxClipboardAvailable():
const e = process.env.DISPLAY;
if (e) {
const t = e.match(/^:(\d+)/);
return !t || existsSync(`/tmp/.X11-unix/X${t[1]}`);
}
return false;
With no DISPLAY it returns false and no copy is attempted. When it does proceed it
uses @crosscopy/clipboard → setText(), which targets the OS clipboard of the
machine the CLI runs on — the remote host, which has no clipboard.
The terminal-escape infrastructure already exists, for paste:
/terminal-setup writes VS Code keybindings using
workbench.action.terminal.sendSequence (e.g. shift+enter, shift+tab).
textOnlyKeyFor() returns a terminal-specific paste sequence when it detects
terminalType === "vscode", with platform branching for darwin/win32/linux.
So paste already routes through the terminal. Copy appears to be missing the
counterpart.
Steps to reproduce the issue
- Connect to a remote Linux host over SSH (Tailscale in my case, but any SSH works),
from a VS Code integrated terminal using Remote-SSH.
- Confirm there is no display:
echo $DISPLAY # empty
ls /tmp/.X11-unix/ # sockets may exist but nothing is listening
- Start Command Code, get any reply.
- Type
/copy.
- Observe: "Could not reach the clipboard — is this a headless terminal?"
Expected: the reply lands in the local machine's clipboard.
The same session in OpenCode copies successfully with ctrl+y.
Command Code Version
1.66.0
Operating System
Linux
Terminal/IDE
vscode
Shell
bash
Session file (optional)
No response
Fix prompt (optional)
In writeClipboardText(), when isClipboardAvailable() returns false — and more
generally whenever the process is running on a different machine from the terminal
emulator — fall back to OSC 52 instead of failing.
What it lives in: the clipboard path in the CLI bundle, reachable from
handleCopy() (the /copy handler). Today it is:
isClipboardAvailable() → isLinuxClipboardAvailable() guard → dynamic import of
@crosscopy/clipboard → setText(). That last step is what needs a sibling.
What "correct" means:
- Build
ESC ] 52 ; c ; <base64-of-utf8-text> BEL and write it to stdout.
- Prefer the local-clipboard path when it actually works; use OSC 52 as the fallback
(or prefer OSC 52 whenever the terminal supports it — isOsc52Supported() in
OpenCode is a reasonable model).
- Guard against very large payloads: OSC 52 has practical size limits, so either
truncate with a warning or keep the local path first so only remote sessions hit
the escape-sequence path.
- Keep
/copy's existing behaviour of reporting the copied character count.
How to check:
- Over SSH with
DISPLAY unset, /copy should report success and the text should
paste into an app on the client machine.
- In a local terminal with a working clipboard, existing behaviour must be unchanged.
- Unit-test the escape-sequence construction (exact bytes, correct base64 of UTF-8)
without spawning a terminal.
- Confirm no regression for the existing
textOnlyKeyFor() paste paths.
Additional context
Workarounds considered and rejected:
- X11 forwarding works, but requires an X server on the client. On modern macOS
(26.04) that means installing XQuartz, since Apple removed the built-in X server.
That is a heavy ask for clipboard access, and clipboard-over-X11 is slow.
- Manual terminal selection works but is unusable for long output.
For comparison, OpenCode ships this as copyToClipboardOSC52(text), guarded by
isOsc52Supported(), in its terminal library — which is why copy works there in the
identical setup.
No session file attached. Happy to provide more detail, test a fix, or follow up.
Summary
/copyfails in every SSH / remote / container session with "Could not reach theclipboard — is this a headless terminal?", because it writes to the OS clipboard of
the machine the CLI runs on rather than sending the text through the terminal.
This is a daily papercut for remote workflows. Copying agent output is a core loop in
a CLI agent, and today it silently degrades to manual terminal selection over SSH.
Notably, OpenCode handles this correctly using OSC 52 (text travels through the
terminal to the client), so
ctrl+yworks there in the exact same setup.Expected Behavior
/copyshould work in remote sessions. Two acceptable designs, in order ofpreference:
OSC 52 — emit
ESC ] 52 ; c ; <base64> BELwhen no local clipboard isavailable. The terminal emulator (VS Code, iTerm2, GNOME Terminal) intercepts it
and writes to the client's clipboard. This is the standard mechanism for remote
CLI clipboard and needs no dependency.
Reuse the existing terminal-escape path that
/terminal-setupalready sets up forpaste, so copy and paste use one consistent mechanism
Actual Behavior
/copyreports "Could not reach the clipboard — is this a headless terminal?" andcopies nothing.
Traced from the shipped bundle (
dist/cli.mjs, minified — so function names areaccurate but there are no usable line numbers):
/copy→handleCopy()→writeClipboardText(), which first callsisClipboardAvailable(). On Linux that reduces toisLinuxClipboardAvailable():With no
DISPLAYit returns false and no copy is attempted. When it does proceed ituses
@crosscopy/clipboard→setText(), which targets the OS clipboard of themachine the CLI runs on — the remote host, which has no clipboard.
The terminal-escape infrastructure already exists, for paste:
/terminal-setupwrites VS Code keybindings usingworkbench.action.terminal.sendSequence(e.g.shift+enter,shift+tab).textOnlyKeyFor()returns a terminal-specific paste sequence when it detectsterminalType === "vscode", with platform branching for darwin/win32/linux.So paste already routes through the terminal. Copy appears to be missing the
counterpart.
Steps to reproduce the issue
from a VS Code integrated terminal using Remote-SSH.
/copy.Expected: the reply lands in the local machine's clipboard.
The same session in OpenCode copies successfully with ctrl+y.
Command Code Version
1.66.0
Operating System
Linux
Terminal/IDE
vscode
Shell
bash
Session file (optional)
No response
Fix prompt (optional)
Additional context
Workarounds considered and rejected:
(26.04) that means installing XQuartz, since Apple removed the built-in X server.
That is a heavy ask for clipboard access, and clipboard-over-X11 is slow.
For comparison, OpenCode ships this as
copyToClipboardOSC52(text), guarded byisOsc52Supported(), in its terminal library — which is why copy works there in theidentical setup.
No session file attached. Happy to provide more detail, test a fix, or follow up.