Skip to content

Commit 0368311

Browse files
guunergoonerclaude
andauthored
fix: prevent iTerm2 terminal response sequences from leaking into REPL input (claude-code-best#172)
The earlyInput capture's escape sequence detection was too simplistic — it only checked if the byte after ESC fell in 0x40-0x7E range, treating it as a terminator. This caused DCS sequences (e.g. XTVERSION `\x1bP>|iTerm2 3.6.4\x1b\\`) and CSI parameter sequences (e.g. DA1 `\x1b[?64;...c`) to partially leak into the input buffer as `>|iTerm2 3.6.4?64;1;2;4;6;17;18;21;22c`. Fix by handling each escape sequence type per ECMA-48: - CSI (`ESC [`): skip parameter + intermediate bytes, then final byte - DCS/OSC/SOS/PM (`ESC P/]/X/^`): scan to BEL or ST terminator - Other: keep single-byte skip Closes claude-code-best#171 Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8976ed6 commit 0368311

1 file changed

Lines changed: 34 additions & 8 deletions

File tree

src/utils/earlyInput.ts

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -102,17 +102,43 @@ function processChunk(str: string): void {
102102
}
103103

104104
// Skip escape sequences (arrow keys, function keys, focus events, etc.)
105-
// All escape sequences start with ESC (0x1B) and end with a byte in 0x40-0x7E
105+
// All escape sequences start with ESC (0x1B).
106106
if (code === 27) {
107107
i++ // Skip the ESC character
108-
// Skip until the terminating byte (@ to ~) or end of string
109-
while (
110-
i < str.length &&
111-
!(str.charCodeAt(i) >= 64 && str.charCodeAt(i) <= 126)
112-
) {
113-
i++
108+
if (i >= str.length) continue
109+
110+
const next = str.charCodeAt(i)!
111+
112+
// CSI sequences: ESC [ ... <final_byte 0x40-0x7E>
113+
// e.g. \x1b[?64;1;2;4;6;17;18;21;22c (DA1 response)
114+
if (next === 0x5b /* [ */) {
115+
i++ // skip '['
116+
// Skip parameter bytes (0x30-0x3F) and intermediate bytes (0x20-0x2F)
117+
while (i < str.length && str.charCodeAt(i)! >= 0x20 && str.charCodeAt(i)! <= 0x3f) {
118+
i++
119+
}
120+
// Skip the final byte (0x40-0x7E)
121+
if (i < str.length && str.charCodeAt(i)! >= 0x40 && str.charCodeAt(i)! <= 0x7e) i++
122+
continue
114123
}
115-
if (i < str.length) i++ // Skip the terminating byte
124+
125+
// String sequences: DCS (P), OSC (]), SOS (X), PM (^)
126+
// These end with BEL (0x07) or ST (ESC \)
127+
if (next === 0x50 /* P */ || next === 0x5d /* ] */ || next === 0x58 /* X */ || next === 0x5e /* ^ */) {
128+
i++ // skip the introducer
129+
while (i < str.length) {
130+
if (str.charCodeAt(i) === 0x07) { i++; break } // BEL terminates
131+
if (str.charCodeAt(i) === 0x1b && i + 1 < str.length && str.charCodeAt(i + 1)! === 0x5c) {
132+
i += 2; break // ESC \ (ST) terminates
133+
}
134+
i++
135+
}
136+
continue
137+
}
138+
139+
// SS2 (N), SS3 (O) — 2-byte sequences, just skip both
140+
// Other simple escape sequences: ESC <byte 0x40-0x7E> — just skip the one byte
141+
if (i < str.length) i++
116142
continue
117143
}
118144

0 commit comments

Comments
 (0)