[Bug] Terminal rendering broken with Bangla language input

Status Open
Reported on v2.1.226
Maintainer reply ✓ Yes — bcherny
Activity 3 comments · opened Aug 10, 2026
💡 Likely answer: A maintainer (bcherny, collaborator) responded on this thread — see the highlighted reply below.

TUI layout corrupts for all complex scripts (Bengali/Indic/SEA): Claude Code measures width with wcwidth and never enables terminal mode 2027

Summary

Chatting with Claude Code in Bengali (and, by the same mechanism, Hindi, Tamil, Telugu, Kannada, Malayalam, Sinhala, Thai, Khmer, Myanmar) corrupts the TUI. The composer border drifts, lines wrap at the wrong column, and redraws leave artifacts. The text becomes unreadable and the interface unusable.

This is not a missing-font problem. It is a text-measurement mismatch between Claude Code and the terminal, and it cannot be fixed by any user-side font or terminal configuration.

Impact

This makes the Claude Code TUI effectively unusable for the writing systems of roughly two billion people. Every Indic script, plus Thai, Khmer, and Myanmar, uses the same combination of features that breaks here (combining vowel signs, conjunct ligatures, reordering marks). Arabic is affected to a lesser degree.

Users hitting this have no available workaround inside the terminal. The only remedy is to abandon the TUI for the web or IDE clients.

<img width="1274" height="812" alt="Image" src="https://github.com/user-attachments/assets/db8a66bd-50fb-4b6c-ab7d-281a726393ae" />

Environment

| | |
|---|---|
| Claude Code | 2.1.226 (~/.local/share/claude/versions/2.1.226, Mach-O arm64) |
| OS | macOS, Darwin 25.2.0, Apple Silicon |
| Terminals reproduced in | Ghostty 1.3.1, and Claude Code's own embedded terminal (TERM_PROGRAM=Orca) |
| Locale | en_US.UTF-8 |
| Fonts | Bengali coverage present and verified (Noto Sans Bengali, Kohinoor Bangla, Bangla MN, Bangla Sangam MN) |

Reproduction

  1. Open Claude Code in any terminal.
  2. Send or receive a message containing Bengali prose, e.g.

বাংলা লিপি বিশ্বের অন্যতম জটিল লিপি। এখানে ব্যঞ্জনবর্ণ, স্বরচিহ্ন আর যুক্তাক্ষর মিলে একেকটি অক্ষরগুচ্ছ তৈরি হয়।

  1. Watch the composer's right-hand border as the line wraps.

Expected: text wraps at the correct column; the box border stays fixed.

Actual: the border shifts left and right by several columns depending on content; wrapped lines break mid-cluster; glyphs overlap into neighbouring cells.

Root cause

Claude Code measures string width per-codepoint using a wcwidth-style table. Under that model, Unicode combining marks score zero columns. Bengali prose is dense with them:

  • ি (U+09BF), (U+09BE), (U+09C7), (U+09CB) — vowel signs that occupy real horizontal space
  • (U+09CD) — hasant, which fuses consonants into a single conjunct glyph

So ব্যঞ্জনবর্ণ (11 codepoints) is measured as ~8 columns but rendered at roughly 12 columns of proportional width. The terminal and the application disagree about where the cursor is, and the layout desyncs.

There is a terminal standard for exactly this problem: mode 2027 (Grapheme Cluster Width). When an application enables it, conforming terminals measure a whole grapheme cluster as one unit, and both sides agree. It is supported by Ghostty, kitty, WezTerm, foot, and Contour.

Claude Code does not enable it. Scanning the 2.1.226 binary for private-mode set/reset sequences:

$ LC_ALL=C grep -ao '\[?[0-9]\{3,4\}[hl]' ~/.local/share/claude/versions/2.1.226 \
    | sort | uniq -c | sort -rn
   3 [?1049l      # alternate screen
   3 [?1049h
   2 [?9001l      # win32 input mode
   2 [?1004l      # focus tracking
   2 [?1004h
   1 [?2026l      # synchronized output
   1 [?2026h
   1 [?1007h      # alternate scroll
   1 [?1006l      # SGR mouse
   1 [?1006h
   1 [?1000l      # mouse tracking
   1 [?1000h

Mode 2026 (synchronized output) is present; mode 2027 is absent. The binary also carries wcwidth and eastasianwidth tables alongside StringWidth and grapheme references, consistent with per-codepoint measurement.

Caveat: this is static analysis of a compiled binary. If the sequence is assembled dynamically at runtime it would not appear as a literal. The observed behaviour matches the absence, but the source is authoritative.

Why users cannot work around this

Ghostty exposes grapheme-width-method, which chooses between the two measurement models. Neither setting resolves the problem, because the mismatch is between the two programs:

| Ghostty setting | Composer border | Glyph rendering |
|---|---|---|
| unicode (default) | drifts — Ghostty says 1 cell, Claude Code says 3 | correct |
| legacy (matches wcwidth) | stable | clusters overlap; prose illegible |

Setting legacy makes both sides agree on counts, which stabilises the border, but wcwidth under-allocates Bengali so severely that glyphs collide. Condensing the font to wdth=62.5 and widening cells 8% reduces the collisions without eliminating them, and degrades Latin/code rendering as a side effect.

Ghostty's own documentation confirms the mechanism:

The default value is unicode … This results in correct grapheme width but may result in cursor-desync issues with some programs … that may use a legacy method such as wcswidth. If a running program explicitly enables terminal mode 2027, then unicode width will be forced regardless of this configuration.

Claude Code is the program that must opt in.

Proposed fix

  1. Emit CSI ? 2027 h on TUI startup, and CSI ? 2027 l on exit alongside the existing alternate-screen teardown.
  2. Measure width by grapheme cluster internally rather than summing per-codepoint wcwidth. Intl.Segmenter with granularity: 'grapheme' is available in the runtime and already partly referenced in the binary.
  3. Both must change together. Enabling 2027 without changing internal measurement moves the desync rather than removing it.
  4. Feature-detect for terminals that lack it. Query with DECRQM (CSI ? 2027 $ p) and fall back to the current behaviour when unsupported, so Terminal.app and others are unaffected.

Current workaround for affected users

Use the non-TUI clients — claude.ai/code, the desktop app's chat window, or the VS Code / JetBrains extensions. These use real text layout rather than a character grid, and complex scripts render correctly with no configuration.

View original on GitHub ↗

3 Comments

dineshkp · 19 days ago

Facing the same issue. I am appending the report generated by claude post my attempted diagnosis to fix the issue.

TUI measures text by grapheme cluster while the terminal advances by codepoint — complex scripts (Tamil/Indic) corrupt the differential renderer

Summary

Claude Code's TUI appears to measure string width by grapheme cluster
(Intl.Segmenter semantics, as used by string-width v7 / modern Ink), but
terminals in their default mode advance the cursor by codepoint width
(wcwidth semantics). For scripts where the two disagree — Tamil, and Indic
scripts generally — the layout drifts by roughly 2× per word, and the
differential renderer then erases and overwrites the wrong cells.

The result is duplicated and overlapping text that makes the session
unreadable once any complex-script text is on screen.

Environment

| | |
|---|---|
| Claude Code | 2.1.227 |
| Terminal | Windows Terminal 1.24.11911.0 |
| OS | Windows 11 + WSL2 (Ubuntu), Linux 6.6.87.2-microsoft-standard-WSL2 |
| Shell | zsh |
| Locale | LANG=C.UTF-8 |
| TERM | xterm-256color |
| Font | MesloLGM Nerd Font, size 15 |

Reproduction

  1. In a WSL shell inside Windows Terminal, run claude.
  2. Have the assistant emit a few paragraphs containing Tamil text — e.g. any

response quoting தனியுரிமைக் கொள்கை or பாதுகாப்பு several times.

  1. Scroll, or let the TUI redraw (a spinner tick or status-line update is

enough).

Text from earlier in the transcript is duplicated, overwritten, and
interleaved. English lines are displaced; Tamil words render broken.

The measurement

Ruler test in a plain shell (no Claude Code involved):

printf '0123456789\n'
printf 'பாதுகாப்பு|\n'

The | lands at column 10, i.e. the terminal advanced 9 cells for
that word.

What the two sides compute for the same string:

| method | result |
|---|---|
| codepoints | 10 |
| wcswidth() (glibc, C.UTF-8) — what the terminal follows | 9 |
| Intl.Segmenter grapheme segments — what string-width v7 / Ink counts | 5 |

const seg = new Intl.Segmenter('en', {granularity: 'grapheme'});
[...seg.segment('பாதுகாப்பு')].length   // => 5

Tamil vowel signs are Unicode category Mc (spacing combining marks), so
wcwidth assigns each one a column, while grapheme segmentation folds them
into the preceding cluster. Every Tamil word therefore disagrees by
approximately its number of vowel signs.

What this is not

Three plausible causes were tested and ruled out:

Not a font or shaping problem. printf 'பாதுகாப்பு\n' in the same
terminal renders the word correctly. The terminal shapes and draws Tamil fine
for a plain run; only application-driven cursor positioning breaks.

Not purely stale paint. Dragging a selection over the corrupted region
forces a repaint, which restores the English text but not the Tamil — so the
English damage is stale paint from displaced cursor positions, while the
layout arithmetic itself is wrong.

Not fixed by DEC mode 2027. Setting grapheme-clustering mode in a plain
shell before launching (printf '\033[?2027h') produced no change, in or out
of Claude Code. Windows Terminal accepts the mode but does not appear to apply
clustering to Indic spacing marks. Note also that no CSI ? 2027 h appears
anywhere in the shipped binary — I grepped for it — so Claude Code is not
negotiating the mode today.

Expected vs actual

Expected: the TUI's width model matches what the attached terminal
actually does, so redraws land on the correct cells.

Actual: the TUI assumes grapheme-cluster widths that no terminal provides
by default, and the frame diff corrupts the screen.

Suggested fix

The safe default is to measure with wcwidth semantics, since that is
what terminals do unless told otherwise. Grapheme-cluster widths should be
used only after mode 2027 has been both requested and confirmed:

  1. Emit CSI ? 2027 h.
  2. Query with DECRQM (CSI ? 2027 $ p) and read the reply.
  3. Use cluster widths only on reply 1 or 3 (set / permanently set).

Otherwise fall back to wcwidth.

A narrower mitigation, if the full negotiation is too invasive: when a line
contains codepoints whose wcwidth and grapheme-cluster widths disagree,
repaint that whole line rather than diffing it. That trades a little
throughput for correctness on exactly the text that currently breaks.

Impact

Any user working in a language whose script uses spacing combining marks —
Tamil, Devanagari, Bengali, Telugu, Kannada, Malayalam, and others — cannot
read a Claude Code session that contains their own language. The present
workaround is to keep all such text out of the terminal entirely.

Incidental finding (not a Claude Code bug)

zsh renders combining marks as <hex> escapes in the line editor unless
setopt COMBINING_CHARS is set — e.g. the Tamil virama U+0BCD displays as
<0bcd> while typing. Unrelated to the above, but it compounds the impression
that the terminal is broken, and it may come up in other reports.

maarufvai · 7 days ago

Confirming this reproduces on Windows too, not just macOS/Ghostty.

Environment

  • Claude Code: 2.1.241
  • OS: Windows 11 Pro 10.0.26200
  • Terminal: VS Code integrated terminal (xterm.js)

Same mechanism as described above (wcwidth-style per-codepoint
measurement undercounting Bangla spacing vowel signs), but on this
setup the corruption concentrated around a specific redraw path: a
repeatedly-updating summary box redrawn each turn. Once Bangla text
appeared in the transcript, later redraws of that box landed at the
wrong column and interleaved with unrelated lines/words from other
turns (e.g. two words from different lines merging into one).

Tried and confirmed had no effect (consistent with this being a
measurement bug, not a config/font issue):

  • terminal.integrated.gpuAcceleration: off
  • terminal.integrated.fontFamily with a Bangla-capable fallback

(Nirmala UI)

  • terminal.integrated.rescaleOverlappingGlyphs: true

The mode 2027 + Intl.Segmenter proposal above matches what we'd
independently guessed at; the extra data point here is that any
incremental/in-place redraw (not just initial line wrap) is affected,
so the fix likely needs the redraw/patch path to also stop assuming
codepoint-count == column offset, not just the initial width
measurement.

bcherny collaborator · 5 days ago

Thank you for the exceptionally thorough report. I can confirm this reproduces on the current release (2.1.233): typing your Bengali sample into the composer corrupts the layout — wrapped lines break mid-cluster, spaces between words are lost, and the text overwrites the composer's border/rule characters. It reproduces in both the default and fullscreen renderers.

One additional data point: I reproduced this inside tmux, which measures width the legacy (wcwidth) way — so the desync isn't only the app↔terminal disagreement on grapheme-capable terminals like Ghostty. Claude Code's own line-wrapping for text dense in combining marks is internally inconsistent as well, which matches your finding that Ghostty's legacy setting stabilizes the border but doesn't make the text usable.

Marking this as a confirmed bug affecting complex scripts (Bengali and other Indic scripts, Thai, Khmer, Myanmar). Until it's fixed, the workarounds are unfortunately the ones you listed: the web, desktop, or IDE clients for extended work in these scripts.

🤖 Generated with Claude Code