anthropics / anthropics/claude-code
Legacy Windows console (conhost): SI byte from reassertTerminalModes renders as a visible glyph in the input box; ambiguous-width glyphs corrupt partial repaints
- Dominant language
- Python
- Stars
- 145k
- Forks
- 23.1k
- PR merge metrics
- PR metrics pending
Description
**Environment:** Claude Code 2.1.237 (native install), Windows 10 Pro 19045, cmd.exe in the legacy console host (conhost, not Windows Terminal), codepage 65001, `HKCU\Console\VirtualTerminalLevel=1`, Korean locale, CJK console font.
## Bug 1: SI (0x0F) printed as a glyph at the cursor
`reassertTerminalModes()` writes the charset reset `"\x1B(B\x0F"` (ESC(B + SI) whenever the event-loop stall monitor detects a >5s stall ("likely sleep/wake") and on stdin resume.
The Win10 inbox conhost VT parser consumes `ESC(B` but does **not** implement SI/SO (LS0/LS1). The 0x0F byte falls through and is stored as a visible cell (rendered with the legacy control-glyph, a sun/bug-like character) at the current cursor position — which, when idle, is inside the prompt input box. Each sleep/wake or throttling stall adds one more glyph; they disappear on the next input-row repaint, so users see mysterious "bug emoji" accumulating while the app is idle.
Verified empirically: writing `A \x1B(B \x0F B` to a VT-enabled Win10 conhost yields buffer cells `A, U+000F, B` and a 3-column cursor advance. Writing `A \x00 B` yields `A, B` (NUL is discarded cleanly).
**Suggested fix:** skip the SI (or the whole charset reset) when the terminal is the legacy Windows console (no `WT_SESSION`, Windows, VT enabled via console API), or replace SI with nothing on win32. DECSC/DECRC + DECSTBM in the restore path were verified fine on conhost; only SI/SO leak.
## Bug 2: East-Asian-ambiguous glyph widths desync cell-diff repaints
The renderer computes string widths with `Bun.stringWidth(s, {ambiguousIsNarrow: true})`, but legacy conhost measures each glyph against the actual console font. With a CJK console font (GulimChe and every other Korean/Japanese font), many UI symbols occupy **2 cells**: `· ← ↓ … ✔ ● ◆ ◇ ○ ✳ ⚠ ※`, and `U+FE0E` (used in `·✔︎·`) occupies 2 extra cells itself. Box-drawing chars stay 1 cell, so no single `ambiguousIsNarrow` setting can match.
Result: absolute-column partial repaints (cell diff) land N columns away from where the previous frame's text actually sits, producing mixed old/new text in status/hint lines — e.g. `(shift+tab to cycle)` displayed as `(shift+eab to cycle)`, dropped or seemingly reordered characters.
Additionally `U+23F5` (the `⏵⏵` auto/plan-mode indicator) appears to be counted emoji-wide (2) by Bun.stringWidth while conhost renders it 1 cell, desyncing that line even with a non-CJK font, and it has no glyph in most monospace fonts (tofu on legacy conhost, which does no font fallback for symbols).
**Suggested fix:** on legacy conhost, prefer full-line repaints over intra-line cell diffs (or verify real cursor advance), and/or use conservative ASCII/narrow symbols (`▸` instead of `⏵`, plain `✔` without VS15) when the legacy console is detected.
## Workarounds found
- Bug 1: byte-patch `\x1B(B\x0F` → `\x1B(B\x00` in the binary (NUL verified harmless on conhost).
- Bug 2: switch the console font to D2Coding (narrow symbol glyphs + native Hangul) — removes almost all width mismatches on ko-KR.
---
*Root-caused and verified with byte-level probes by [Claude Code](https://claude.com/claude-code) running on the affected setup.*
Contributor guide
No contributing guide indexed for this repository
Research direction
Start at reassertTerminalModes and trace the restore path that writes the charset reset, then inspect the renderer's Bun.stringWidth and cell-diff repaint logic. Reproduce the byte-level probes and partial repaint behavior in legacy conhost. Done means SI no longer appears as an input glyph and affected lines repaint without width-driven desynchronization.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- bun
- Domain
- cli, operating-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100