Skip to content

Commit 01e4ebe

Browse files
alse-symclaude
andcommitted
fix(clipboard): write OSC52 to /dev/tty instead of stdout
Fixes clipboard copy failing in terminal multiplexers like Zellij, tmux, and screen. These tools intercept OSC52 escape sequences written to stdout but do not forward them to the outer terminal. Writing to /dev/tty (or SSH_TTY when connected via SSH) bypasses the multiplexer's PTY layer and sends OSC52 directly to the terminal emulator. Falls back to stdout if /dev/tty is unavailable (e.g., on Windows). Fixes anomalyco#12082 Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent af06175 commit 01e4ebe

1 file changed

Lines changed: 16 additions & 1 deletion

File tree

‎packages/opencode/src/cli/cmd/tui/util/clipboard.ts‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,34 @@ import clipboardy from "clipboardy"
44
import { lazy } from "../../../../util/lazy.js"
55
import { tmpdir } from "os"
66
import path from "path"
7+
import fs from "fs"
78

89
/**
910
* Writes text to clipboard via OSC 52 escape sequence.
1011
* This allows clipboard operations to work over SSH by having
1112
* the terminal emulator handle the clipboard locally.
13+
*
14+
* Writes to /dev/tty directly to bypass terminal multiplexers
15+
* (Zellij, tmux, screen) that intercept OSC52 on stdout.
1216
*/
1317
function writeOsc52(text: string): void {
1418
if (!process.stdout.isTTY) return
1519
const base64 = Buffer.from(text).toString("base64")
1620
const osc52 = `\x1b]52;c;${base64}\x07`
1721
const passthrough = process.env["TMUX"] || process.env["STY"]
1822
const sequence = passthrough ? `\x1bPtmux;\x1b${osc52}\x1b\\` : osc52
19-
process.stdout.write(sequence)
23+
24+
// Write to /dev/tty directly to bypass terminal multiplexers.
25+
// SSH_TTY is set when connected via SSH, otherwise use /dev/tty.
26+
const ttyPath = process.env["SSH_TTY"] || "/dev/tty"
27+
try {
28+
const fd = fs.openSync(ttyPath, "w")
29+
fs.writeSync(fd, sequence)
30+
fs.closeSync(fd)
31+
} catch {
32+
// Fallback to stdout if /dev/tty is unavailable (e.g., Windows)
33+
process.stdout.write(sequence)
34+
}
2035
}
2136

2237
export namespace Clipboard {

0 commit comments

Comments
 (0)