Skip to content

fix(clipboard): correct UTF-8 for terminal copy/paste (OSC 52)#44

Open
egertaia wants to merge 1 commit into
amirlehmam:masterfrom
egertaia:fix/clipboard-utf8
Open

fix(clipboard): correct UTF-8 for terminal copy/paste (OSC 52)#44
egertaia wants to merge 1 commit into
amirlehmam:masterfrom
egertaia:fix/clipboard-utf8

Conversation

@egertaia

Copy link
Copy Markdown

Problem

Copying text containing multi-byte UTF-8 characters from a TUI app (Claude Code, or tmux with set-clipboard on) and pasting it back produced mojibake — e.g. an em dash became â€".

Root cause: these apps copy via the OSC 52 clipboard escape sequence, whose payload is base64-encoded UTF-8. The handler decoded it with atob(), which returns a binary/Latin-1 string (one code point per byte), so the em dash bytes E2 80 94 turned into code points U+00E2 U+0080 U+0094.

Plain xterm text-selection copy goes through navigator.clipboard.writeText() and was unaffected — which is exactly why "copy from the terminal works, copy from Claude doesn't."

Fix

  • OSC 52 decode: decode the base64 payload as UTF-8 (TextDecoder over the raw bytes) instead of atob()'s Latin-1 string. (the actual bug)
  • Paste hardening (consistency):
    • Read the clipboard via Electron's clipboard.readText() instead of navigator.clipboard.readText() (no focus/permission requirement).
    • Route the Ctrl+Shift+V paste action through the terminal so it honors bracketed-paste mode — multi-line paste no longer submits on the first newline (e.g. pasting into Claude Code).

Verification

Reproduced with an instrumented build logging clipboard reads/writes:

  • Before: copying foo — bar from a TUI logged U+00E2 U+0080 U+0094.
  • After: same copy logs U+2014, and the paste round-trips as foo — bar.

🤖 Generated with Claude Code

Copying text from a TUI app (Claude Code, tmux with set-clipboard on) routes
through the OSC 52 clipboard-write escape sequence, whose payload is base64
UTF-8. The handler decoded it with atob(), which yields a binary/Latin-1
string (one code point per byte), so multi-byte characters were mangled — an
em dash (E2 80 94) became "â€" (U+00E2 U+0080 U+0094). Decode the base64 bytes
as UTF-8 via TextDecoder instead. (Plain xterm selection copy was unaffected,
which is why copying from the terminal worked but copying from Claude didn't.)

Also harden the paste side to match:
- Read the clipboard through Electron's clipboard.readText() (more reliable
  than navigator.clipboard, which needs focus/permission).
- Route the Ctrl+Shift+V paste action through the terminal so it honors
  bracketed-paste mode (multi-line paste no longer submits on the first \n).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant