Selecting text in the TUI corrupts multi-byte UTF-8 (Cyrillic) when copied via OSC 52

Status Open
Reported on v2.1.168
Maintainer reply None cached
Activity 5 comments · opened Jun 7, 2026

Description

When selecting non-ASCII text (Cyrillic, and likely any multi-byte UTF-8) inside the Claude Code TUI, the copy is routed through OSC 52 and the resulting clipboard content is mojibake. ASCII/Latin characters in the same selection are preserved; only multi-byte characters are corrupted.

The footer hint confirms the OSC 52 path: sent 28 chars via OSC 52 · if paste fails, hold Shift while selecting for native copy.

Steps to reproduce

  1. Run Claude Code in a terminal that supports OSC 52 (here: integrated terminal in VS Code, Remote-SSH).
  2. Have the TUI display some Cyrillic text (e.g. Главный честный вывод вечера).
  3. Select it with a normal mouse drag (TUI captures the selection → OSC 52).
  4. Paste the clipboard anywhere.

Expected

Clipboard contains the original UTF-8 text, e.g. Главный честный вывод вечера.

Actual

Clipboard contains mojibake, e.g. ÐавĐ¾Đ¹ ÑеÑÑĐ½Đ¹ Đ²ÑĐ¾Đ´ Đ²ĐµĐµÑа — the classic pattern of UTF-8 bytes encoded/decoded as a single-byte codepage. This looks like the OSC 52 base64 step is treating the string as latin1/single-byte instead of UTF-8 before base64 encoding.

Workaround (also confirms root cause)

Holding Shift while selecting (Shift+click / Shift+drag) bypasses the TUI's mouse capture and uses the terminal's native copy. The native path produces correct UTF-8. So the corruption is isolated to the TUI's OSC 52 copy path, not the terminal, OS, or locale.

Confirmed NOT the cause

  • Remote locale is fine: LANG=en_US.UTF-8, /etc/default/locale UTF-8, interactive shell UTF-8.
  • Native VS Code terminal copy/paste of the exact same text round-trips correctly (verified by reading the Windows clipboard byte-for-byte).
  • Pasting known-good UTF-8 into the TUI renders correctly — only the OSC 52 copy direction is broken.
  • Not a renderer issue (terminal.integrated.gpuAcceleration: "off" made no difference to this path).

Suspected fix

In the OSC 52 copy implementation, base64-encode the UTF-8 bytes of the selection (e.g. Buffer.from(text, 'utf8').toString('base64')), not a latin1/binary view of the JS string.

Environment

  • Claude Code: 2.1.168
  • Terminal: VS Code integrated terminal, Remote-SSH
  • Client OS: Windows 11
  • Remote OS: Ubuntu 24.04 (noble)
  • Shell: bash

View original on GitHub ↗

5 Comments

yurukusa · 2 months ago

Reproduced this and can confirm your root cause and fix, plus two details that should help whoever picks it up.

Your fix is correct. With your sample string Главный честный вывод вечера:

// what the OSC 52 path should do:
Buffer.from(text, 'utf8').toString('base64')
// -> 0JPQu9Cw0LLQvdGL0Lkg0YfQtdGB0YLQvdGL0Lkg0LLRi9Cy0L7QtCDQstC10YfQtdGA0LA=
// receiver base64-decodes -> "Главный честный вывод вечера"  ✅

Surfacing the string through a latin1/binary view before base64 (the bug) yields the classic ÐлавнÑй… mojibake — each 2-byte UTF-8 sequence shows up as two single-byte codepoints. So Buffer.from(text, 'utf8') (or any explicit UTF-8 → bytes → base64) fixes it.

Detail 1 — the footer corroborates it. sent 28 chars via OSC 52: that string is 28 UTF-16 code units (.length), but 53 UTF-8 bytes. So the OSC 52 code is measuring (and almost certainly encoding) in JS string units, not bytes — exactly the units mismatch behind the latin1 base64 step.

Detail 2 — don't be thrown by the exact glyphs in the report. The canonical bug output is ÐлавнÑй… (all from U+00C0–U+00FF). The glyphs pasted in the issue (ÐавĐ¾Đ¹… — note Đ is U+0110 and there's even a stray real Cyrillic в) are that corrupted clipboard text getting mangled a second time when copied into the GitHub editor. The underlying transform is the standard UTF-8-bytes-as-latin1; the odd characters are downstream noise, not a second distinct bug.

Scope: this hits any multi-byte UTF-8 copied via the TUI's OSC 52 path — Cyrillic, CJK, emoji, accented Latin — so it's a silent clipboard-corruption for a large set of users, not just Cyrillic. The Shift+select workaround is correct because it bypasses the TUI capture entirely and uses the terminal's native copy.

thomasballinger · 2 months ago

This is a VS Code bug, not Claude Code. VS Code 1.123 and 1.124 ship @xterm/addon-clipboard@0.3.0-beta.220, whose OSC 52 handler decodes the base64 payload with bare atob() and writes the resulting Latin-1 binary string to the clipboard without a UTF-8 decode so every multi-byte character becomes â…. Claude Code is sending correctly-encoded UTF-8.

Tracked and fixed upstream: xtermjs/xterm.js#6000 → xtermjs/xterm.js#6002, picked up by VS Code in microsoft/vscode#320646.

Fix: update VS Code to 1.125 or later (or stay on ≤1.122).
Workaround on 1.123/1.124: mouse-select + Cmd/Ctrl+C in the terminal — that copies the rendered cells and bypasses OSC 52.

mreza0100 · 2 months ago

Upstream root cause + fix — this is a VSCode/xterm.js bug, already patched upstream.

The TUI-selection → OSC 52 corruption reported here lives in VSCode's bundled @xterm/addon-clipboard, not in Claude Code itself.

Symptom: multi-byte UTF-8 copied from the integrated terminal (Cyrillic, CJK, emoji, em-dash , box-drawing) arrives mojibake'd — e.g. (e2 80 94) pastes as â€". Started in VSCode 1.123.0.

Why: @xterm/addon-clipboard's Base64.encodeText/decodeText use raw btoa/atob, which operate on Latin-1 byte-strings. Any multi-byte UTF-8 is therefore corrupted on the OSC 52 base64 round-trip. Tracked as xterm.js #6000.

Fix: xterm.js PR #6002 ("fix non-ascii clipboard handling"), merged 2026-06-06. VSCode side: microsoft/vscode#319821 (closed, milestone 1.125.0; #319853 is a dup). Not on the stable channel yet (stable is 1.124.2) — present in Insiders 1.126.0-insider today.

Workaround until 1.125.0 stable, for anyone running tmux inside the integrated terminal:

set -g set-clipboard off
# pipe copies straight to the OS clipboard, bypassing OSC 52 entirely:
bind -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel pbcopy   # macOS; xclip / wl-copy on Linux

This routes copies to the OS pasteboard directly and never round-trips through OSC 52. Caveat: it only covers tmux-mediated copies (plain mouse-drag) — a VSCode-native selection (Option/Alt-drag) still hits the unpatched decoder until 1.125.0 lands.

CF-DV · 2 months ago

"CLAUDE_CODE_DISABLE_MOUSE": "1" helped me. I use CC in dev containers.

benbuckman · 1 month ago

This is labeled stale, but is still occurring for my team. Our stack is: MacOS > VS Code > SSH to Coder workspace (Linux) > tmux > claude.
There are workarounds, but it would be better for this to be fixed in Claude Code itself.
Thanks!