/copy writes the clipboard as latin-1, so non-ASCII output pastes as mojibake (still broken in 2.1.215)

Status Open
Reported on v2.1.215
Maintainer reply ✓ Yes — bcherny
Activity 4 comments · opened Jul 20, 2026
💡 Likely answer: A maintainer (bcherny, collaborator) responded on this thread — see the highlighted reply below.

Summary

/copy writes the clipboard as latin-1, so any non-ASCII output pastes as mojibake. Still broken in 2.1.215. The dump file /copy writes to /tmp/claude-*/response.md is clean UTF-8, and pbcopy < that_same_file produces a correct clipboard, so the corruption is in the clipboard write and not in the source text. I confirmed the exact transformation rather than guessing at it (details below).

This was reported before in #66065 and #66421. #66065 was closed by the inactivity bot with "please open a new issue if this is still relevant", and #66421 was auto-closed as a duplicate of it. Neither was fixed, so filing fresh per that message.

Measured root cause

The clipboard write takes UTF-8 bytes and treats them as latin-1. I predicted the corrupted size from the clean file before measuring it:

raw = open('/tmp/claude-501/response.md','rb').read()   # what /copy itself dumped
len(raw)                                                # 2372, valid UTF-8

# hypothesis: those UTF-8 bytes get decoded as latin-1, then re-encoded as UTF-8
len(raw.decode('latin-1').encode('utf-8'))              # 4109

Measured clipboard via pbpaste | wc -c: 4109 bytes. Predicted: 4109 bytes. Exact match, so the transformation is utf-8 bytes -> decode latin-1 -> encode utf-8. Not a font or rendering issue.

The paste agrees: 好了,给你两条路 arrives as 好äºï¼ç»ä½ 两æ¡è·¯, which is E5 A5 BD (好) read as three separate latin-1 characters. pbpaste | file - on the bad clipboard also reports NEL line terminators, which is 0x85 from the same mangling.

Possible common factor: VS Code / Cursor integrated terminals

I went back through the earlier reports and the terminal is the thing they share, which may be more useful than the OS:

| Issue | Environment |
|---|---|
| #66065 | VS Code, devcontainer |
| #72731 | Cursor integrated terminal, SSH to Debian |
| #73284 | Cursor integrated terminal |
| this one | VS Code-family integrated terminal (TERM_PROGRAM=vscode), macOS, local, no SSH or container |

Mine is the local macOS case with pbcopy available on PATH and still broken, which rules out the "xclip invoked without a UTF8_STRING target" explanation that fits the Linux reports. #66098 separately points at OSC 52 for a different copy path.

To be clear about what is measured and what is not: the byte-level mechanism above is measured. The guess that the integrated-terminal clipboard path (likely OSC 52 rather than pbcopy) is the one being chosen and mangled is a hypothesis from the correlation, not something I verified. I only have the one terminal to test in.

Reproduction

  1. macOS, Claude Code 2.1.215, VS Code-family integrated terminal
  2. Get any assistant output containing non-ASCII (CJK, Cyrillic, accented Latin, em dash, emoji)
  3. Run /copy
  4. Compare pbpaste | wc -c against wc -c /tmp/claude-*/response.md

The clipboard comes out larger than the file it was built from, and pasting anywhere shows mojibake. ASCII-only output is fine.

Workaround, which is also what isolates it:

pbcopy < /tmp/claude-501/response.md   # clipboard now byte-identical to the file

Expected

Clipboard bytes identical to the dump file that /copy already writes correctly.

Proposed fix

Write the clipboard from the same UTF-8 bytes used for the dump file, instead of letting the text go through a latin-1 or default single-byte encode on the way to the clipboard call. Since the dump file is already correct, the encoding is being dropped at the handoff.

Cheap regression test: assert clipboard_bytes == dumpfile_bytes for a string with at least one character above U+007F.

Related

  • #66065 same /copy bug, closed by the inactivity bot rather than fixed
  • #66421 same bug, auto-closed as a duplicate of #66065
  • #72731, #73284 open, Cursor terminal, same encoding family
  • #66098 OSC 52 path specifically
  • #30865, #42417 older Windows variants

Environment

  • Claude Code 2.1.215
  • macOS 26.5.2 (build 25F84), arm64
  • TERM_PROGRAM=vscode, TERM_PROGRAM_VERSION=3.11.13, TERM=xterm-256color
  • LANG=en_US.UTF-8
  • pbcopy / pbpaste present on PATH
  • No SSH, no container, no WSL

---

I hit this in normal use, measured it, and drafted the writeup with Claude Code, then reviewed and edited it before filing.

View original on GitHub ↗

3 Comments

annexiao · 1 month ago

Update: I have a deterministic repro now. It is not intermittent, it needs two conditions at once, which I think explains why the earlier reports on this disagree with each other.

It breaks only when the fullscreen renderer is active AND the copied text contains box-drawing / symbol characters (U+2500 range, U+2605). Either one alone is fine.

| | text has box-drawing / ★ | text has none |
|---|---|---|
| "tui": "fullscreen" | BROKEN (2 for 2) | OK |
| "tui": "default" | OK | OK |

Every run measured the same way: /copy writes a dump file, then compare pbpaste | wc -c against wc -c on that file, immediately, before anything else touches the clipboard.

tui         source   clipboard   latin-1 prediction   result
fullscreen   2372      4109            4109           BROKEN
fullscreen   2899      2899             -             OK
default      2876      2876             -             OK
fullscreen   2876      5321            5321           BROKEN

Both broken runs land exactly on len(raw.decode('latin-1').encode('utf-8')), so the corruption is the same latin-1 round trip described in the original report. The dump file is clean UTF-8 in every case, including the broken ones, so only the clipboard write is affected.

Length is not the variable. The longest sample (2899 bytes) was the one that came out clean; it just had no box-drawing characters in it.

What I could not separate: both broken samples contained U+2500-range box-drawing and U+2605 together, so I cannot say which of the two triggers it, or whether it is the whole ambiguous-width class. The broken sample had 174 box-drawing characters and 6 symbols. Em dash (U+2014) appears in a clean sample, so that one is not enough on its own.

Guessing at the mechanism, and this part is a guess: the fullscreen renderer has to compute display width for every character itself, where the default renderer leaves that to the terminal. Ambiguous-width characters are exactly the ones that path has to make a call on, so a bug there could corrupt the buffer that later feeds the clipboard while the on-screen rendering still looks correct. That matches what I see, since the text displays fine and only the copy is wrong.

If that is right, this and #66269 are the same bug seen from two sides. #66269 reported the renderer as the cause and found "tui": "default" fixes it; other reporters on fullscreen presumably were copying text without those characters and saw nothing wrong. The interaction would explain both.

Environment unchanged from the original report: Claude Code 2.1.215, macOS 26.5.2 arm64, TERM_PROGRAM=vscode, LANG=en_US.UTF-8.

Repro, if useful:

  1. /tui fullscreen
  2. get assistant output containing a box-drawing rule (a run of ) plus CJK or any non-ASCII
  3. /copy
  4. pbpaste | wc -c vs wc -c /tmp/claude-*/response.md, they differ, and the clipboard size equals the latin-1 round trip of the file
  5. /tui default and repeat the same steps, sizes now match
bcherny collaborator · 15 days ago

Could not reproduce on v2.1.233 (macOS 26, arm64) in iTerm2 and in tmux inside iTerm2, with the fullscreen renderer on and text containing CJK + a run of box-drawing + .

Steps:

  1. Start claude with /tui fullscreen, ask for a reply containing 好了,给你两条路 ★ ─────────── café — résumé.
  2. Run /copy.
  3. Immediately compare pbpaste | wc -c with wc -c /tmp/claude-*/response.md.

Observed: both 81 bytes, cmp reports the clipboard and the dump file identical, paste is correct.
Expected (per the report): clipboard larger than the file, latin-1 mojibake on paste.

Assessment: this looks like a genuine bug, but with two halves. The byte pattern you measured (each UTF-8 byte re-encoded as one Latin-1 character) is exactly what a known bug in the terminal-side OSC 52 clipboard handler shipped by VS Code 1.123 and 1.124 produces (fixed upstream in VS Code 1.125; forks such as Cursor pick that up on their own schedule, and TERM_PROGRAM_VERSION=3.11.13 is a Cursor version). That is why it never shows up in iTerm2/tmux here. On our side, /copy on a local macOS session writes the clipboard twice: once through pbcopy (correct, and what the docs describe as the local path — https://code.claude.com/docs/en/fullscreen) and once through an OSC 52 escape sequence as a safety net. In a terminal with that decoder bug the OSC 52 write lands after pbcopy and overwrites the good result with the mangled one, which matches everything you saw: the dump file is fine, pbcopy < response.md fixes it, and ASCII-only text is unaffected. The fullscreen-vs-default and box-drawing correlations are most likely timing (a heavier redraw shifts when the terminal processes the OSC 52 write relative to pbcopy) rather than a renderer encoding bug. Claude Code already shows a warning for VS Code 1.123/1.124, but its version check does not recognise Cursor's version numbers, so you got no warning either. Two things would fix this properly on our side: skip the OSC 52 write locally when a native clipboard tool already succeeded on a VS Code-family terminal, and extend the warning to Cursor. In the meantime, a current VS Code build (1.125 or later) or Terminal.app/iTerm2 should give clean pastes; if you can confirm it is clean there, that pins it to the terminal decoder plus our double write.

🤖 Generated with Claude Code

github-actions[bot] · 15 days ago

We weren't able to reproduce this. Could you provide steps to trigger the issue — what you ran, what happened, and what you expected? This issue will be closed automatically if there's no activity within 7 days.

Showing cached comments. Read the full discussion on GitHub ↗