Input parser types the tail of SGR mouse reports split across starved reads

Status Open
Maintainer reply None cached
Activity 1 comment ยท opened Jul 18, 2026

Body:

๐Ÿค– Filed by an AI agent on behalf of ranveer@squareup.com.

Summary. Under heavy host load, when Claude Code's TUI receives an SGR mouse report
(ESC [ < b ; x ; y M) split across multiple reads, the input parser appears to time out on the
escape prefix and treat the remainder as typed text: the report's tail is inserted into the
composer as literal characters.

Observed. Composer content after wheel activity over a loaded pane (host load ~60, tmux 3.6a,
macOS): <65;59;28M5;59;28M โ€” two adjacent wheel-down reports, one missing its first 2 bytes
(ESC [), the next missing 4 (ESC [ < 6). The irregular truncation points are the signature of
sequences severed at read boundaries rather than any fixed-length framing bug.

Repro shape (synthetic). Write an SGR report into the TUI's stdin in two chunks with a delay
between them (e.g. ESC [ < โ€ฆ 50โ€“100ms โ€ฆ 65;59;28M). Expected: the report is either consumed
or discarded whole. Actual (under load): the bytes after the timed-out escape prefix are typed
into the composer.

Repro (burst, reliable). No host load needed: write N complete 12-byte reports back-to-back
into the TUI's pty (we used 200 whole ESC [ < 65 ; x ; y M sequences in ~9ms โ€” every write a
complete sequence, no artificial splitting). With the TUI busy (mid-tool-run), 1โ€“2 reports per
burst re-enter as composer text (observed: <65;41;21M typed into an idle session's composer).
Read-boundary chunking on the receiving side is what severs them โ€” writer-side atomicity cannot
prevent it, only reader-side partial-escape carry can. At human wheel rates (~10โ€“30 reports/s)
we could not reproduce it; the burst makes it deterministic enough for a regression test.

Why it matters. A mouse report that re-enters as text lands in a composer that can submit โ€”
in agent-automation setups (many terminals, one loaded host) this happens repeatedly. We now
drop expired reports at our own mux seam, but the parser-side fragility affects any Claude Code
user on a loaded machine with mouse tracking active.

Suggested direction. For CSI sequences with a < private marker, prefer buffering until the
final byte (M/m) or a much longer escape timeout, and on timeout DISCARD the partial sequence
rather than flushing it into the input as text.

View original on GitHub โ†—

This issue has 1 comment on GitHub. Read the full discussion on GitHub โ†—