Skip to content

Clipboard copy fails in Zellij and other multiplexers (OSC52 written to stdout instead of /dev/tty) #12082

@jackm-intel

Description

@jackm-intel

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:

  1. stdout goes to Zellij's PTY infrastructure
  2. Zellij intercepts OSC52 sequences for its own clipboard handling
  3. Zellij does NOT passthrough child process OSC52 to the outer terminal
  4. 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"
  • osc tool: Opens /dev/tty directly using tcell.NewDevTtyFromDev()

Reproduction Steps

  1. Install OpenCode v1.1.50 or v1.1.51
  2. Run OpenCode inside Zellij: zellij run -- opencode
  3. Copy any text using OpenCode's copy function
  4. Notification shows "Text copied to clipboard"
  5. 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/tty for 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.

Metadata

Metadata

Assignees

Labels

opentuiThis relates to changes in v1.0, now that opencode uses opentui

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions