[BUG] TUI copy/paste includes trailing whitespace padding and ANSI escapes — Ink renderer uses space-padding instead of \x1b[K

Open 💬 4 comments Opened Feb 4, 2026 by lotto3

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Describe the bug
Copying text from Claude Code's TUI output includes trailing whitespace (spaces padded to full terminal width) and ANSI escape sequences (\x1b[7m/\x1b[27m reverse video). This makes copied code blocks unusable when pasted into SSH sessions, editors, or other terminals.

This is a design-level problem, not an edge case.
Claude Code is a CLI tool whose primary purpose is generating code that developers need to use. Clean copy/paste is not a nice-to-have — it is the most basic functional requirement of a code-generation tool that runs in a terminal. Every ncurses-based terminal application — nano, vim, htop, tmux — has respected the terminal contract for decades: use \x1b[K (erase to end of line) for empty space, so terminals know those cells aren't content. The decision to use Ink's full-cell painting approach (filling every empty cell with actual space characters and reverse video attributes) prioritizes cosmetic TUI polish over the fundamental utility of the tool. The agent itself is excellent — but none of that matters if users can't get the code out of the terminal. A developer copying code from Claude Code into an SSH session, a file, or another terminal and getting 160 bytes of space padding and ANSI escape sequences per line is a tool that has failed at its core job. Please prioritize terminal correctness over visual styling.

Expected behavior
Copying text from the TUI should produce clean plaintext with no trailing whitespace or escape sequences, matching behavior of other terminal UIs (nano, htop, vim, etc.).

Root cause
The Ink renderer's normal rendering path (render() method in cli.js, line 761) pads every screen line with actual space characters (0x20) to the terminal width, wrapped in reverse video escape sequences. ncurses-based TUIs use \x1b[K (erase to end of line) which marks those cells as empty — terminals exclude empty cells from copies.

The debug rendering path (getRenderOpsDebug(), line 760) already handles this correctly by calling .trimEnd() on each line. The normal path does not.

Evidence (xxd of pasted content received over SSH)

000000c0: 346d 7e1b 5b30 306d 2420 1b5b 376d 6e61  4m~.[00m$ .[7mna
000000d0: 6d65 7320 3d20 5b22 416c 6963 6522 5d20  mes = ["Alice"]
000000e0: 2020 2020 2020 2020 2020 2020 2020 2020
000000f0: 2020 2020 2020 2020 2020 2020 2020 2020
[... ~160 bytes of 0x20 space padding to terminal width ...]
00000170: 2020 2020 2020 2020 2020 2020 2020 201b                 .
00000180: 5b32 376d 1b5b 376d 201b 5b32 376d 1b5b  [27m.[7m .[27m.[

Pattern per line: \x1b[7m (reverse video on) → content text → space padding to terminal width → \x1b[27m (reverse video off)

Only Claude Code is affected. nano, htop, cat output, and node stdout all copy clean on the same system.

Onset behavior — this bug is not always present
This is not a static configuration issue. The bug onsets unpredictably during normal use:

  • Machine 1 (MacBook Pro, macOS 15.5): Bug appeared during a working session. Persisted through reboots, cache clears, OS upgrade to 15.7.3, version rollback. No recovery possible.
  • Machine 2 (MacBook Air, macOS 15.7.3): Switched to this machine to escape the bug. Copy/paste worked perfectly for a full day (all day Monday). Then tonight, after hours of clean operation, the bug onset here too — same symptoms, same machine, no configuration changes.
  • There is nothing in the user's session history or configuration that triggers it — it happens on a fresh terminal after a reboot on a machine where it previously worked fine.
  • Once it onsets, there is no recovery path. The user is permanently locked out of clean copy/paste with no workaround.
  • This is not a long-history issue — the user does not have extensive session history. The bug appears regardless.

Real-world impact

  • Pasting Claude Code output into SSH sessions produces corrupted commands — heredocs fail, multi-line Python has syntax errors from trailing spaces, shell commands break
  • The only workaround is writing code to local files and SCP'ing to remote servers — turning a 10-second paste into a multi-step file transfer process
  • For developers using Claude Code with remote infrastructure (EC2, cloud VMs, Docker), this bug makes the tool nearly unusable for its core purpose
  • Once the bug onsets on a machine, there is no escape — the user cannot recover clean copy/paste behavior

Suggested fix
In the normal render path, after each line's content, emit \x1b[K (erase to end of line) instead of space-padding. Alternatively, apply trimEnd() to output lines as the debug path already does. This is how ncurses-based TUIs work and why they copy clean.

Code location in cli.js (v2.1.27)

  • Line 760: getRenderOpsDebug() — debug path, uses w.trimEnd(), output is clean
  • Line 761: render() — normal path, branches on if(this.options.debug), falls through to space-padded rendering
  • Lines 766-769: t07() function — normal path character-by-character rendering, no trimEnd

Environment

  • Claude Code: 2.1.27 and 2.1.29 (both affected, latest available at time of testing)
  • macOS: Sequoia 15.5 and 15.7.3 (both affected, two separate machines)
  • Terminal: Apple Terminal 455.1 and iTerm2 (both affected)
  • Node: v22.15.1
  • TERM: xterm-256color

Attempted fixes (none worked)
NO_COLOR=1, TERM=dumb, CLAUDE_CODE_ACCESSIBILITY=true, --debug, iTerm2 "trim trailing whitespace on copy", Terminal.app CopyAttributesProfile=0, clearing all caches, fresh ~/.claude, OS upgrade (15.5 → 15.7.3), version rollback (2.1.29 → 2.1.27), deleting Terminal.app saved state, machine reboot, switching to a completely different machine (worked temporarily, then bug onset there too).

What Should Happen?

Describe the bug
Copying text from Claude Code's TUI output includes trailing whitespace (spaces padded to full terminal width) and ANSI escape sequences (\x1b[7m/\x1b[27m reverse video). This makes copied code blocks unusable when pasted into SSH sessions, editors, or other terminals.

This is a design-level problem, not an edge case.
Claude Code is a CLI tool whose primary purpose is generating code that developers need to use. Clean copy/paste is not a nice-to-have — it is the most basic functional requirement of a code-generation tool that runs in a terminal. Every ncurses-based terminal application — nano, vim, htop, tmux — has respected the terminal contract for decades: use \x1b[K (erase to end of line) for empty space, so terminals know those cells aren't content. The decision to use Ink's full-cell painting approach (filling every empty cell with actual space characters and reverse video attributes) prioritizes cosmetic TUI polish over the fundamental utility of the tool. The agent itself is excellent — but none of that matters if users can't get the code out of the terminal. A developer copying code from Claude Code into an SSH session, a file, or another terminal and getting 160 bytes of space padding and ANSI escape sequences per line is a tool that has failed at its core job. Please prioritize terminal correctness over visual styling.

Expected behavior
Copying text from the TUI should produce clean plaintext with no trailing whitespace or escape sequences, matching behavior of other terminal UIs (nano, htop, vim, etc.).

Root cause
The Ink renderer's normal rendering path (render() method in cli.js, line 761) pads every screen line with actual space characters (0x20) to the terminal width, wrapped in reverse video escape sequences. ncurses-based TUIs use \x1b[K (erase to end of line) which marks those cells as empty — terminals exclude empty cells from copies.

The debug rendering path (getRenderOpsDebug(), line 760) already handles this correctly by calling .trimEnd() on each line. The normal path does not.

Evidence (xxd of pasted content received over SSH)

000000c0: 346d 7e1b 5b30 306d 2420 1b5b 376d 6e61  4m~.[00m$ .[7mna
000000d0: 6d65 7320 3d20 5b22 416c 6963 6522 5d20  mes = ["Alice"]
000000e0: 2020 2020 2020 2020 2020 2020 2020 2020
000000f0: 2020 2020 2020 2020 2020 2020 2020 2020
[... ~160 bytes of 0x20 space padding to terminal width ...]
00000170: 2020 2020 2020 2020 2020 2020 2020 201b                 .
00000180: 5b32 376d 1b5b 376d 201b 5b32 376d 1b5b  [27m.[7m .[27m.[

Pattern per line: \x1b[7m (reverse video on) → content text → space padding to terminal width → \x1b[27m (reverse video off)

Only Claude Code is affected. nano, htop, cat output, and node stdout all copy clean on the same system.

Onset behavior — this bug is not always present
This is not a static configuration issue. The bug onsets unpredictably during normal use:

  • Machine 1 (MacBook Pro, macOS 15.5): Bug appeared during a working session. Persisted through reboots, cache clears, OS upgrade to 15.7.3, version rollback. No recovery possible.
  • Machine 2 (MacBook Air, macOS 15.7.3): Switched to this machine to escape the bug. Copy/paste worked perfectly for a full day (all day Monday). Then tonight, after hours of clean operation, the bug onset here too — same symptoms, same machine, no configuration changes.
  • There is nothing in the user's session history or configuration that triggers it — it happens on a fresh terminal after a reboot on a machine where it previously worked fine.
  • Once it onsets, there is no recovery path. The user is permanently locked out of clean copy/paste with no workaround.
  • This is not a long-history issue — the user does not have extensive session history. The bug appears regardless.

Real-world impact

  • Pasting Claude Code output into SSH sessions produces corrupted commands — heredocs fail, multi-line Python has syntax errors from trailing spaces, shell commands break
  • The only workaround is writing code to local files and SCP'ing to remote servers — turning a 10-second paste into a multi-step file transfer process
  • For developers using Claude Code with remote infrastructure (EC2, cloud VMs, Docker), this bug makes the tool nearly unusable for its core purpose
  • Once the bug onsets on a machine, there is no escape — the user cannot recover clean copy/paste behavior

Suggested fix
In the normal render path, after each line's content, emit \x1b[K (erase to end of line) instead of space-padding. Alternatively, apply trimEnd() to output lines as the debug path already does. This is how ncurses-based TUIs work and why they copy clean.

Code location in cli.js (v2.1.27)

  • Line 760: getRenderOpsDebug() — debug path, uses w.trimEnd(), output is clean
  • Line 761: render() — normal path, branches on if(this.options.debug), falls through to space-padded rendering
  • Lines 766-769: t07() function — normal path character-by-character rendering, no trimEnd

Environment

  • Claude Code: 2.1.27 and 2.1.29 (both affected, latest available at time of testing)
  • macOS: Sequoia 15.5 and 15.7.3 (both affected, two separate machines)
  • Terminal: Apple Terminal 455.1 and iTerm2 (both affected)
  • Node: v22.15.1
  • TERM: xterm-256color

Attempted fixes (none worked)
NO_COLOR=1, TERM=dumb, CLAUDE_CODE_ACCESSIBILITY=true, --debug, iTerm2 "trim trailing whitespace on copy", Terminal.app CopyAttributesProfile=0, clearing all caches, fresh ~/.claude, OS upgrade (15.5 → 15.7.3), version rollback (2.1.29 → 2.1.27), deleting Terminal.app saved state, machine reboot, switching to a completely different machine (worked temporarily, then bug onset there too).

Error Messages/Logs

**Describe the bug**
Copying text from Claude Code's TUI output includes trailing whitespace (spaces padded to full terminal width) and ANSI escape sequences (`\x1b[7m`/`\x1b[27m` reverse video). This makes copied code blocks unusable when pasted into SSH sessions, editors, or other terminals.

**This is a design-level problem, not an edge case.**
Claude Code is a CLI tool whose primary purpose is generating code that developers need to use. Clean copy/paste is not a nice-to-have — it is the most basic functional requirement of a code-generation tool that runs in a terminal. Every ncurses-based terminal application — nano, vim, htop, tmux — has respected the terminal contract for decades: use `\x1b[K` (erase to end of line) for empty space, so terminals know those cells aren't content. The decision to use Ink's full-cell painting approach (filling every empty cell with actual space characters and reverse video attributes) prioritizes cosmetic TUI polish over the fundamental utility of the tool. The agent itself is excellent — but none of that matters if users can't get the code out of the terminal. A developer copying code from Claude Code into an SSH session, a file, or another terminal and getting 160 bytes of space padding and ANSI escape sequences per line is a tool that has failed at its core job. Please prioritize terminal correctness over visual styling.

**Expected behavior**
Copying text from the TUI should produce clean plaintext with no trailing whitespace or escape sequences, matching behavior of other terminal UIs (nano, htop, vim, etc.).

**Root cause**
The Ink renderer's normal rendering path (`render()` method in cli.js, line 761) pads every screen line with actual space characters (0x20) to the terminal width, wrapped in reverse video escape sequences. ncurses-based TUIs use `\x1b[K` (erase to end of line) which marks those cells as empty — terminals exclude empty cells from copies.

The debug rendering path (`getRenderOpsDebug()`, line 760) already handles this correctly by calling `.trimEnd()` on each line. The normal path does not.

**Evidence (xxd of pasted content received over SSH)**

000000c0: 346d 7e1b 5b30 306d 2420 1b5b 376d 6e61  4m~.[00m$ .[7mna
000000d0: 6d65 7320 3d20 5b22 416c 6963 6522 5d20  mes = ["Alice"]
000000e0: 2020 2020 2020 2020 2020 2020 2020 2020
000000f0: 2020 2020 2020 2020 2020 2020 2020 2020
[... ~160 bytes of 0x20 space padding to terminal width ...]
00000170: 2020 2020 2020 2020 2020 2020 2020 201b                 .
00000180: 5b32 376d 1b5b 376d 201b 5b32 376d 1b5b  [27m.[7m .[27m.[


Pattern per line: `\x1b[7m` (reverse video on) → content text → space padding to terminal width → `\x1b[27m` (reverse video off)

**Only Claude Code is affected.** nano, htop, cat output, and node stdout all copy clean on the same system.

**Onset behavior — this bug is not always present**
This is not a static configuration issue. The bug onsets unpredictably during normal use:
- Machine 1 (MacBook Pro, macOS 15.5): Bug appeared during a working session. Persisted through reboots, cache clears, OS upgrade to 15.7.3, version rollback. No recovery possible.
- Machine 2 (MacBook Air, macOS 15.7.3): Switched to this machine to escape the bug. Copy/paste worked perfectly for a full day (all day Monday). Then tonight, after hours of clean operation, the bug onset here too — same symptoms, same machine, no configuration changes.
- There is nothing in the user's session history or configuration that triggers it — it happens on a fresh terminal after a reboot on a machine where it previously worked fine.
- Once it onsets, there is no recovery path. The user is permanently locked out of clean copy/paste with no workaround.
- This is not a long-history issue — the user does not have extensive session history. The bug appears regardless.

**Real-world impact**
- Pasting Claude Code output into SSH sessions produces corrupted commands — heredocs fail, multi-line Python has syntax errors from trailing spaces, shell commands break
- The only workaround is writing code to local files and SCP'ing to remote servers — turning a 10-second paste into a multi-step file transfer process
- For developers using Claude Code with remote infrastructure (EC2, cloud VMs, Docker), this bug makes the tool nearly unusable for its core purpose
- Once the bug onsets on a machine, there is no escape — the user cannot recover clean copy/paste behavior

**Suggested fix**
In the normal render path, after each line's content, emit `\x1b[K` (erase to end of line) instead of space-padding. Alternatively, apply `trimEnd()` to output lines as the debug path already does. This is how ncurses-based TUIs work and why they copy clean.

**Code location in cli.js (v2.1.27)**
- Line 760: `getRenderOpsDebug()` — debug path, uses `w.trimEnd()`, output is clean
- Line 761: `render()` — normal path, branches on `if(this.options.debug)`, falls through to space-padded rendering
- Lines 766-769: `t07()` function — normal path character-by-character rendering, no trimEnd

**Environment**
- Claude Code: 2.1.27 and 2.1.29 (both affected, latest available at time of testing)
- macOS: Sequoia 15.5 and 15.7.3 (both affected, two separate machines)
- Terminal: Apple Terminal 455.1 and iTerm2 (both affected)
- Node: v22.15.1
- TERM: xterm-256color

**Attempted fixes (none worked)**
`NO_COLOR=1`, `TERM=dumb`, `CLAUDE_CODE_ACCESSIBILITY=true`, `--debug`, iTerm2 "trim trailing whitespace on copy", Terminal.app `CopyAttributesProfile=0`, clearing all caches, fresh `~/.claude`, OS upgrade (15.5 → 15.7.3), version rollback (2.1.29 → 2.1.27), deleting Terminal.app saved state, machine reboot, switching to a completely different machine (worked temporarily, then bug onset there too).

Steps to Reproduce

Describe the bug
Copying text from Claude Code's TUI output includes trailing whitespace (spaces padded to full terminal width) and ANSI escape sequences (\x1b[7m/\x1b[27m reverse video). This makes copied code blocks unusable when pasted into SSH sessions, editors, or other terminals.

This is a design-level problem, not an edge case.
Claude Code is a CLI tool whose primary purpose is generating code that developers need to use. Clean copy/paste is not a nice-to-have — it is the most basic functional requirement of a code-generation tool that runs in a terminal. Every ncurses-based terminal application — nano, vim, htop, tmux — has respected the terminal contract for decades: use \x1b[K (erase to end of line) for empty space, so terminals know those cells aren't content. The decision to use Ink's full-cell painting approach (filling every empty cell with actual space characters and reverse video attributes) prioritizes cosmetic TUI polish over the fundamental utility of the tool. The agent itself is excellent — but none of that matters if users can't get the code out of the terminal. A developer copying code from Claude Code into an SSH session, a file, or another terminal and getting 160 bytes of space padding and ANSI escape sequences per line is a tool that has failed at its core job. Please prioritize terminal correctness over visual styling.

Expected behavior
Copying text from the TUI should produce clean plaintext with no trailing whitespace or escape sequences, matching behavior of other terminal UIs (nano, htop, vim, etc.).

Root cause
The Ink renderer's normal rendering path (render() method in cli.js, line 761) pads every screen line with actual space characters (0x20) to the terminal width, wrapped in reverse video escape sequences. ncurses-based TUIs use \x1b[K (erase to end of line) which marks those cells as empty — terminals exclude empty cells from copies.

The debug rendering path (getRenderOpsDebug(), line 760) already handles this correctly by calling .trimEnd() on each line. The normal path does not.

Evidence (xxd of pasted content received over SSH)

000000c0: 346d 7e1b 5b30 306d 2420 1b5b 376d 6e61  4m~.[00m$ .[7mna
000000d0: 6d65 7320 3d20 5b22 416c 6963 6522 5d20  mes = ["Alice"]
000000e0: 2020 2020 2020 2020 2020 2020 2020 2020
000000f0: 2020 2020 2020 2020 2020 2020 2020 2020
[... ~160 bytes of 0x20 space padding to terminal width ...]
00000170: 2020 2020 2020 2020 2020 2020 2020 201b                 .
00000180: 5b32 376d 1b5b 376d 201b 5b32 376d 1b5b  [27m.[7m .[27m.[

Pattern per line: \x1b[7m (reverse video on) → content text → space padding to terminal width → \x1b[27m (reverse video off)

Only Claude Code is affected. nano, htop, cat output, and node stdout all copy clean on the same system.

Onset behavior — this bug is not always present
This is not a static configuration issue. The bug onsets unpredictably during normal use:

  • Machine 1 (MacBook Pro, macOS 15.5): Bug appeared during a working session. Persisted through reboots, cache clears, OS upgrade to 15.7.3, version rollback. No recovery possible.
  • Machine 2 (MacBook Air, macOS 15.7.3): Switched to this machine to escape the bug. Copy/paste worked perfectly for a full day (all day Monday). Then tonight, after hours of clean operation, the bug onset here too — same symptoms, same machine, no configuration changes.
  • There is nothing in the user's session history or configuration that triggers it — it happens on a fresh terminal after a reboot on a machine where it previously worked fine.
  • Once it onsets, there is no recovery path. The user is permanently locked out of clean copy/paste with no workaround.
  • This is not a long-history issue — the user does not have extensive session history. The bug appears regardless.

Real-world impact

  • Pasting Claude Code output into SSH sessions produces corrupted commands — heredocs fail, multi-line Python has syntax errors from trailing spaces, shell commands break
  • The only workaround is writing code to local files and SCP'ing to remote servers — turning a 10-second paste into a multi-step file transfer process
  • For developers using Claude Code with remote infrastructure (EC2, cloud VMs, Docker), this bug makes the tool nearly unusable for its core purpose
  • Once the bug onsets on a machine, there is no escape — the user cannot recover clean copy/paste behavior

Suggested fix
In the normal render path, after each line's content, emit \x1b[K (erase to end of line) instead of space-padding. Alternatively, apply trimEnd() to output lines as the debug path already does. This is how ncurses-based TUIs work and why they copy clean.

Code location in cli.js (v2.1.27)

  • Line 760: getRenderOpsDebug() — debug path, uses w.trimEnd(), output is clean
  • Line 761: render() — normal path, branches on if(this.options.debug), falls through to space-padded rendering
  • Lines 766-769: t07() function — normal path character-by-character rendering, no trimEnd

Environment

  • Claude Code: 2.1.27 and 2.1.29 (both affected, latest available at time of testing)
  • macOS: Sequoia 15.5 and 15.7.3 (both affected, two separate machines)
  • Terminal: Apple Terminal 455.1 and iTerm2 (both affected)
  • Node: v22.15.1
  • TERM: xterm-256color

Attempted fixes (none worked)
NO_COLOR=1, TERM=dumb, CLAUDE_CODE_ACCESSIBILITY=true, --debug, iTerm2 "trim trailing whitespace on copy", Terminal.app CopyAttributesProfile=0, clearing all caches, fresh ~/.claude, OS upgrade (15.5 → 15.7.3), version rollback (2.1.29 → 2.1.27), deleting Terminal.app saved state, machine reboot, switching to a completely different machine (worked temporarily, then bug onset there too).

Claude Model

Opus

Is this a regression?

Yes, this worked in a previous version

Last Working Version

Not version specific

Claude Code Version

Not version specific

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Terminal.app (macOS)

Additional Information

Full analysis with root cause, code locations, xxd evidence, and onset timeline is in the "What's Wrong" section above. The Ink renderer's normal path (cli.js line 761) space-pads lines instead of using \x1b[K. The debug path (line 760) already handles this correctly with trimEnd(). Affects two separate machines (MacBook Pro + MacBook Air), all tested versions (2.1.27, 2.1.29), all terminals (Terminal.app, iTerm2). No user-accessible workaround exists.

View original on GitHub ↗

This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗