[BUG] Bracketed-paste block followed by a key in the same chunk drops the pasted text (breaks CJK IME input from mobile SSH clients like Termius)

Status Open
Reported on v2.1.252
Maintainer reply None cached
Activity 0 comments · opened Sep 1, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet (related but without a byte-level repro: #15705, #27857)
  • [x] This is a single bug report
  • [x] I am using the latest version of Claude Code (2.1.252)

What's Wrong?

When a bracketed-paste block is immediately followed by a plain key in the same stdin chunk, Claude Code drops the pasted text. The plain key (space) is kept.

ESC[200~녕ESC[201~<space>      → "녕" is lost, only the space is inserted

This is exactly what mobile SSH clients produce for CJK IME input. Claude Code enables bracketed paste (ESC[?2004h); Termius (Android and iOS) then wraps every IME-committed Hangul syllable in paste markers. On a slow link the following space/enter is coalesced into the same TCP packet as the paste block, and the syllable before every space disappears. On a fast link the space arrives as a separate chunk and everything works — which is why the bug looks intermittent and "network dependent" to users.

Raw bytes captured from Termius 7.8.2 (Android, Samsung Keyboard) with the same terminal modes Claude Code enables (?1049h ?1000h ?1002h ?1003h ?1006h ?2004h ?1004h ?2031h):

  7214ms  16B 1b 5b 32 30 30 7e eb 85 95 1b 5b 32 30 31 7e 20   '\x1b[200~녕\x1b[201~ '        ← syllable lost
  8961ms  46B ...                                                '\x1b[200~하\x1b[201~\x1b[200~세\x1b[201~\x1b[200~요\x1b[201~ '  ← all three lost
 19674ms  15B 1b 5b 32 30 30 7e ec a7 80 1b 5b 32 30 31 7e      '\x1b[200~지\x1b[201~'
 19674ms   1B 20                                                 ' '                              ← separate chunk: works

Without bracketed paste enabled the same client sends plain 녕 and Claude Code handles it fine. Codex CLI and Gemini CLI in the identical setup receive the text correctly.

I believe #15705 (closed as not planned) and #27857 are the same bug seen from the user side; this report adds the exact byte sequence and a phone-free reproduction.

What Should Happen?

The paste block should be inserted and then the key processed, i.e. 안 + ESC[200~녕ESC[201~ should yield 안녕 in the input box.

Error Messages/Logs

# Results from the harness below, feeding bytes into a real `claude` (2.1.252) running in a pty,
# then forcing a redraw (SIGWINCH) and reading the prompt line.
[P1 [안] | [녕]+sp (one chunk)]   -> '❯ 안'                 # 녕 dropped
[P2 [안] | [녕] | sp (separate)]  -> '❯ 안녕'               # OK
[P3 [하][세][요]+sp (one chunk)] -> '❯'                     # all dropped
[P4 replay of phone capture]      -> '❯ 안반가'             # 녕, 하세요, 워 dropped
# (spaces are not visible in the capture because Ink renders them as cursor moves; the syllables are what matters)

Steps to Reproduce

No phone needed. On Linux/macOS, in a trusted project directory:

# repro.py — run: python3 repro.py
import os, pty, select, time, re, signal, fcntl, termios, struct
ANSI = re.compile(rb'\x1b\[[0-9;?]*[ -/]*[@-~]|\x1b\][^\x07]*\x07|\x1b[PX^_].*?\x1b\\|\x1b[()][A-Za-z0-9]|\x1b[=>]')
BP = lambda t: b"\x1b[200~" + t.encode() + b"\x1b[201~"

def drain(fd, t):
    end = time.time() + t; out = b""
    while time.time() < end:
        r, _, _ = select.select([fd], [], [], 0.05)
        if r:
            try: c = os.read(fd, 65536)
            except OSError: break
            if not c: break
            out += c
    return out

def run(name, steps, cols=60):
    pid, fd = pty.fork()
    if pid == 0:
        os.environ["TERM"] = "xterm-256color"; os.environ.pop("CLAUDECODE", None)
        os.execvp("claude", ["claude"])
    fcntl.ioctl(fd, termios.TIOCSWINSZ, struct.pack("HHHH", 24, cols, 0, 0))
    drain(fd, 7.0)                                  # wait for the TUI
    for data, gap in steps:
        os.write(fd, data); drain(fd, gap)
    # resize away and back -> Ink does a full redraw, so the prompt line is captured whole
    fcntl.ioctl(fd, termios.TIOCSWINSZ, struct.pack("HHHH", 24, cols + 1, 0, 0)); os.kill(pid, signal.SIGWINCH); drain(fd, 0.5)
    fcntl.ioctl(fd, termios.TIOCSWINSZ, struct.pack("HHHH", 24, cols, 0, 0)); os.kill(pid, signal.SIGWINCH)
    txt = ANSI.sub(b"", drain(fd, 1.0)).decode("utf-8", "replace").replace("\r", "\n")
    lines = [l.strip() for l in txt.split("\n") if "❯" in l]
    print(f"[{name}] -> {lines[-1] if lines else '(no prompt line)'!r}")
    os.write(fd, b"\x03"); time.sleep(0.3); os.write(fd, b"\x03"); time.sleep(0.4)
    os.kill(pid, signal.SIGKILL); os.waitpid(pid, 0)

run("P1 paste+space in ONE chunk (bug)", [(BP("안"), 0.4), (BP("녕") + b" ", 0.3)])
run("P2 paste, then space in SEPARATE chunk (ok)", [(BP("안"), 0.4), (BP("녕"), 0.3), (b" ", 0.3)])
run("P3 three pastes + space in ONE chunk (bug)", [(BP("하") + BP("세") + BP("요") + b" ", 0.3)])

Expected: P1 → 안녕, P3 → 하세요. Actual: P1 → 안, P3 → empty. (Nothing is submitted; no API calls are made.)

Manual reproduction: SSH from Termius on Android/iOS with a Korean keyboard, type any Hangul word followed by a space over a non-LAN connection.

Claude Model

Not sure / Multiple models

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.252 (Claude Code)

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

Other

Additional Information

  • Server: Ubuntu, kernel 6.8, TERM=xterm-256color, LANG=en_US.UTF-8; client: Termius 7.8.2 on Samsung Galaxy S23 (Samsung Keyboard). Termius identifies itself as ESC P>|Termius 7.8.2 ESC \ / DA1 ESC[?1;2c.
  • Workaround that fully fixes it for me: a small pty wrapper between the SSH session and claude that rewrites short single-line paste blocks (ESC[200~X ESC[201~, X ≤ 48 bytes, no newline) into plain X before forwarding. With that in place P1/P3/P4 all come through intact, and real multi-line pastes still work because their markers are left alone.
  • Suggested fix on Claude Code's side: when parsing an input chunk, flush the pasted text when ESC[201~ is seen and continue parsing the remainder of the chunk as normal key input, instead of discarding the paste when other bytes follow the end marker in the same chunk.

View original on GitHub ↗