|
| 1 | +# Terminal query suppression experiment |
| 2 | + |
| 3 | +## Issue (GitHub #271) |
| 4 | + |
| 5 | +The web terminal renders raw escape sequences such as |
| 6 | +`^[]10;rgb:f4f4/f7f7/fbfb^[\` and `^[[?1;2c` inside Claude Code's |
| 7 | +prompt area, which makes navigation and rendering look broken. |
| 8 | + |
| 9 | +## Root cause |
| 10 | + |
| 11 | +TUI applications (Claude Code, Ultraplan, etc.) probe the terminal with |
| 12 | +queries like: |
| 13 | + |
| 14 | +- `\x1b]10;?\x1b\\` – ask for the foreground color (OSC 10). |
| 15 | +- `\x1b]11;?\x1b\\` – ask for the background color (OSC 11). |
| 16 | +- `\x1b]12;?\x1b\\` – ask for the cursor color (OSC 12). |
| 17 | +- `\x1b]4;<n>;?\x1b\\` – ask for an indexed palette color (OSC 4). |
| 18 | +- `\x1b[c` – primary device attributes query (DA1). |
| 19 | +- `\x1b[>c` – secondary device attributes (DA2). |
| 20 | +- `\x1b[=c` – tertiary device attributes (DA3). |
| 21 | +- `\x1b[6n` – cursor position report (CPR). |
| 22 | + |
| 23 | +`xterm.js@5.3.0` responds to all of those out-of-the-box. Because the |
| 24 | +web terminal is fronted by `xterm.js`, the responses are emitted via |
| 25 | +`Terminal.onData` and we forward them to the host PTY as user input. |
| 26 | +Claude Code receives these bytes as keystrokes inside its prompt loop |
| 27 | +and renders them verbatim, which is exactly what the screenshot in the |
| 28 | +issue shows. |
| 29 | + |
| 30 | +## Fix |
| 31 | + |
| 32 | +We install a small parser shim immediately after instantiating the |
| 33 | +`Terminal` (see `terminal-query-suppression.ts`): |
| 34 | + |
| 35 | +- For `OSC 4/10/11/12` we intercept the handler chain. If the payload |
| 36 | + contains a `?` segment (query), we return `true` to consume the |
| 37 | + sequence without invoking xterm's default handler that would |
| 38 | + otherwise reply. Plain "set color" payloads return `false` so the |
| 39 | + default handler still applies the requested theme change. |
| 40 | +- For DA1/DA2/DA3 (`CSI ... c`) and CPR (`CSI ... n`) we always |
| 41 | + return `true` so xterm never reports back to the PTY. None of the |
| 42 | + features that depend on those responses are useful for our headless |
| 43 | + web frontend. |
| 44 | + |
| 45 | +The handlers are returned as disposables so callers (and the unit |
| 46 | +tests) can roll the registration back without touching `xterm`'s |
| 47 | +internal parser state. |
| 48 | + |
| 49 | +## Manual reproduction notes |
| 50 | + |
| 51 | +1. Start the web build (`bun run docker-git -- browser`) and open the |
| 52 | + web terminal. |
| 53 | +2. Inside the container run a TUI that probes color (for example |
| 54 | + `bash -c 'printf "\\033]10;?\\033\\\\"'`). |
| 55 | +3. Without the fix the printed escape sequence is echoed back into the |
| 56 | + prompt as garbage. With the fix nothing is echoed. |
0 commit comments