-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Description
Description
Clipboard copy functionality fails when OpenCode runs inside Zellij (and potentially other terminal multiplexers). The copy notification appears but the clipboard remains empty.
Root Cause
OpenCode writes OSC52 escape sequences to process.stdout, but when running inside Zellij:
- stdout goes to Zellij's PTY infrastructure
- Zellij intercepts OSC52 sequences for its own clipboard handling
- Zellij does NOT passthrough child process OSC52 to the outer terminal
- Result: OSC52 sequences are consumed by Zellij and never reach the actual terminal emulator
Why Other Tools Work
Tools like lazygit and neovim successfully copy to clipboard in Zellij because they write OSC52 sequences directly to /dev/tty (or $SSH_TTY in SSH sessions), which bypasses the multiplexer's PTY layer entirely.
Example from working tools:
- lazygit config:
copyToClipboardCmd: "echo {{text}} | osc copy -d /dev/tty" osctool: Opens/dev/ttydirectly usingtcell.NewDevTtyFromDev()
Reproduction Steps
- Install OpenCode v1.1.50 or v1.1.51
- Run OpenCode inside Zellij:
zellij run -- opencode - Copy any text using OpenCode's copy function
- Notification shows "Text copied to clipboard"
- Attempt to paste - clipboard is empty
Expected Behavior
Text should be copied to the system clipboard, just like when OpenCode runs outside of Zellij.
Environment
- OpenCode version: v1.1.50, v1.1.51 (broken)
- Terminal multiplexer: Zellij (latest)
- Terminal emulator: WezTerm (supports OSC52)
- OS: Linux
- Works outside Zellij: Yes
- Works in v1.1.48: Yes (version before PR Use opentui OSC52 clipboard, again #11744)
Technical Details
Breaking change introduced in PR #11744 (merged Feb 2, 2026)
- Commit: 3982c7d
- File:
packages/opencode/src/cli/cmd/tui/util/clipboard.ts - Function:
writeOsc52()
Current implementation:
// Writes to stdout - fails in multiplexers
process.stdout.write(osc52Sequence);
Proposed fix:
// Try /dev/tty first (bypasses multiplexer), fall back to stdout
try {
const ttyPath = process.env.SSH_TTY || '/dev/tty';
const ttyFd = fs.openSync(ttyPath, 'w');
fs.writeSync(ttyFd, osc52Sequence);
fs.closeSync(ttyFd);
} catch (error) {
// Fallback to stdout if /dev/tty unavailable
process.stdout.write(osc52Sequence);
}
Workaround
Downgrade to v1.1.48 and disable auto-updates:
opencode upgrade v1.1.48
Add to opencode.json:
{
"autoupdate": false
}
References
- Zellij OSC52 passthrough behavior: Child process OSC52 is NOT passed through to outer terminal
- Working implementation example: https://github.com/theimpostor/osc (uses
/dev/tty) - Similar pattern in neovim: Uses
/dev/ttyfor OSC52 clipboard operations
Impact
This affects all users running OpenCode in terminal multiplexers (Zellij, tmux, screen) where the multiplexer intercepts OSC52 sequences. This is a common workflow for developers who use multiplexers for session management.