[BUG] TUI input broken on macOS: cursor position responses (^[[row;colR]) leak to display instead of being consumed

Resolved 💬 3 comments Opened Jan 12, 2026 by lcatlett Closed Feb 27, 2026

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?

Claude Code's interactive TUI is completely unusable on macOS without a tmux workaround. Every keypress causes visible escape codes (^[[35;1R) and the TUI freezes. The issue occurs because DSR 6 (Device Status Report) cursor position query responses are appearing on the display instead of being consumed from stdin.

Symptoms:

  1. When Claude Code starts, the command line flickers
  2. Each keypress causes visible escape sequences like ^[[35;1R to appear above the input
  3. Input is not properly registered—appears to start a new session for each keypress
  4. The TUI becomes completely unresponsive

Tested across multiple terminals—all fail:

  • iTerm2 3.6.6 — ❌ FAILS (CPR leaks in both normal and raw modes)
  • iTerm2 3.6.7beta1 — ❌ FAILS
  • macOS Terminal.app — ❌ FAILS
  • tmux 3.6a — ✅ WORKS (workaround, buffers CPR responses)

Root cause hypothesis: Diagnostic testing confirms the CPR response leaks to display in both normal and raw modes when running directly in iTerm2. This suggests the TUI library either:

  1. Sends DSR 6 (\e[6n) before fully switching to raw mode (-echo -icanon)
  2. Has a timing window where the CPR arrives before stdin reading begins
  3. Fails to properly consume the CPR from the input buffer

tmux works because it creates its own PTY layer that intercepts and buffers cursor position responses before they reach the application's display.

What Should Happen?

The cursor position query response (\e[row;colR) should be silently consumed from stdin by the TUI, not echoed to the display. Interactive mode should function normally on macOS terminals without requiring tmux.

Error Messages/Logs

# DSR 6 query test - this should be invisible but appears on screen:
$ printf '\e[6n'
^[[35;1R


**Debug logs show no errors - initialization succeeds:**
- All plugins load correctly
- MCP servers initialize
- REPL mounts successfully
- No hook errors

The failure occurs at terminal input handling, not initialization

Steps to Reproduce

  1. Install Claude Code via Homebrew:

``bash
brew install --cask claude-code
``

  1. Open any terminal (iTerm2, Terminal.app) on macOS
  1. Run:

``bash
claude
``

  1. Observe flickering and escape codes (^[[35;1R) on any keypress

Additional test matrix:

| Mode | Result |
|------|--------|
| claude -p "hello" (non-interactive) | ✅ Works |
| claude (interactive) | ❌ Fails |
| TERM=dumb claude -p "hello" | ✅ Works |
| TERM=dumb claude (interactive) | ❌ Fails |
| script -q /dev/null claude | ❌ Fails |
| tmux new-session claude | ✅ Works |

Claude Model

Sonnet (default)

Is this a regression?

I don't know

Last Working Version

unknown — issue may have always existed on this configuration

Claude Code Version

2.1.5 (Homebrew cask)

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

iTerm2

Additional Information

Environment Details

macOS: 15.2 (25C56)
Architecture: Apple Silicon (arm64)
Kernel: Darwin 25.2.0
Shell: zsh 5.9 (arm-apple-darwin24.2.0) at /opt/homebrew/bin/zsh
TERM: xterm-256color
COLORTERM: truecolor
LANG: en_US.UTF-8
Installation: /opt/homebrew/bin/claude -> /opt/homebrew/Caskroom/claude-code/2.1.5/claude

Workaround

Running Claude Code inside tmux resolves the issue. However, specific tmux configuration is required to prevent a secondary issue where Claude Code's status bar duplicates/flickers.

Required ~/.tmux.conf:

# CRITICAL: Disable tmux status bar to prevent conflict with Claude Code's status
set -g status off

# Eliminate escape sequence delay (prevents flicker)
set -sg escape-time 0

# Standard settings
set -g default-terminal "tmux-256color"
set -ga terminal-overrides ",xterm-256color:Tc"
set -g mouse on
set -g history-limit 50000
set -g aggressive-resize on
set -g focus-events on

Wrapper script (~/bin/claude-safe):

#!/bin/bash
# Run Claude Code in tmux to work around macOS PTY escape sequence bug

# If non-interactive flags passed, run directly
for arg in "$@"; do
    case "$arg" in
        -p|--print|-h|--help|--version)
            exec /opt/homebrew/bin/claude "$@"
            ;;
    esac
done

# If already in tmux, run directly
if [ -n "$TMUX" ]; then
    exec /opt/homebrew/bin/claude "$@"
fi

# Generate unique session name based on current directory
DIR_HASH=$(echo "$PWD" | md5 | cut -c1-6)
SESSION_NAME="cc-${DIR_HASH}"

# Reuse existing session or create new one
if tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
    exec tmux attach-session -t "$SESSION_NAME"
else
    exec tmux new-session -s "$SESSION_NAME" "/opt/homebrew/bin/claude $*"
fi

Shell aliases (~/.zshrc):

alias claude='claude-safe'
alias cc='claude-safe'
alias claude-raw='/opt/homebrew/bin/claude'  # Direct access if needed

Suggested Fixes

  1. Ensure raw mode before DSR query: Switch terminal to raw mode (-echo -icanon) before sending \e[6n, ensuring CPR responses are consumed from stdin rather than echoed.
  1. Use alternate cursor query methods: Some TUI frameworks (e.g., Charm's bubbletea) use different approaches to avoid CPR/F-key collisions. The \e[?6n private modifier responds as \e[?row;colR in supporting terminals.
  1. Add a --no-tui or --simple mode: Provide a basic line-input mode that avoids cursor position queries entirely.
  1. Detect and recover gracefully: If CPR responses are detected in unexpected locations, fall back to a simpler input mode.

Related Issues

This may be the root cause behind multiple open issues. The pattern [53;1R or similar visible in #578 is the same CPR response format:

  • #578 — Input field freezes in certain folders with [53;1R visible
  • #13287 — Multiple Claude Code instances freeze on macOS due to file locking
  • #13224 — Claude Code hangs or freezes during execution

See also: open TUI bugs%20label%3Aarea%3Atui)

Diagnostic Script

A diagnostic script is available to help others reproduce and verify this issue:

#!/bin/bash
# Claude Code TUI Diagnostic - Run in FAILING terminal (not tmux)

echo "=== System Info ==="
echo "macOS: $(sw_vers -productVersion) ($(sw_vers -buildVersion))"
echo "Shell: $SHELL ($($SHELL --version 2>&1 | head -1))"
echo "TERM: $TERM"
echo "TERM_PROGRAM: $TERM_PROGRAM ($TERM_PROGRAM_VERSION)"
echo "TMUX: ${TMUX:-not set}"
echo ""

echo "=== DSR 6 Escape Sequence Leak Test ==="
echo "If you see ^[[row;colR below, your terminal has the bug:"
echo ""
printf '\e[6n'
sleep 0.5
echo ""
echo "(Line above should be BLANK)"
echo ""

echo "=== Raw Mode Test ==="
old_stty=$(stty -g < /dev/tty 2>/dev/null)
stty raw -echo < /dev/tty 2>/dev/null
printf '\e[6n'
sleep 0.2
read -t 0.5 -s -n 20 raw_response < /dev/tty 2>/dev/null
stty "$old_stty" < /dev/tty 2>/dev/null
echo "Raw mode response: ${raw_response:-none captured}"
echo ""

echo "=== Interpretation ==="
echo "- CPR leaks in normal mode only: timing race in TUI init"
echo "- CPR leaks in both modes: terminal/PTY issue (this bug)"
echo "- CPR never leaks: your terminal handles this correctly"

Diagnostic Output Comparison

| Environment | DSR 6 Query | CPR Response Leak | Raw Mode Capture |
|-------------|-------------|-------------------|------------------|
| tmux 3.6a | Sent | Buffered (invisible) | None |
| iTerm2 direct | Sent | ^[[25;1R visible | [25;31R[25;1R |
| Terminal.app | Sent | ^[[row;colR visible | Leaked |

Interpretation: The CPR response leaks to display in both normal and raw modes when running directly in macOS terminals. tmux works as a workaround because it creates its own PTY layer that intercepts cursor position responses before they reach the application's display.

View original on GitHub ↗

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