Tamil script output collapses TUI rendering (combining-mark width bug) — v2.1.220 macOS

Status Open
Maintainer reply None cached
Activity 4 comments · opened Jul 29, 2026

Description

Printing Tamil script in Claude Code's chat output collapses the TUI: overlapping glyphs, misaligned columns, and a corrupted redraw of the whole conversation area. The renderer appears to assume one display column per code point, but roughly 40% of characters in running Tamil text are combining marks (vowel signs and viramas, Unicode category M) that occupy zero columns.

This is the same defect class as the reported Thai (#81183, currently open on this same version), Devanagari (#53968), and Bengali (#66586) issues, hitting Tamil on macOS.

Environment

  • Claude Code v2.1.220
  • macOS (Darwin 25.5.0), default terminal, zsh

Steps to reproduce

  1. Start claude in any project.
  2. Ask for any response containing a sentence of Tamil script, e.g.:

> வணக்கம், இன்று எப்படி இருக்கீர்கள்?

  1. Observe the rendered output and subsequent redraws.

Expected

Tamil text renders with correct column accounting (grapheme-cluster width, not per-code-point width); surrounding UI stays aligned.

Actual

Glyphs overlap, columns shift for the rest of the frame, and the redraw corrupts previously rendered lines. The same corruption occurs when viewing diffs of files containing Tamil (e.g. via the edit-preview UI), so it affects both chat output and tool-result rendering.

Notes

  • Affects any script whose text is dense in combining marks (Devanagari, Arabic, Hebrew, Thai, Korean jamo behave similarly per the linked issues).
  • Workaround in use: avoid emitting Indic script to the terminal entirely and use Latin transliteration in chat, which is lossy.

View original on GitHub ↗

3 Comments

vahidshaik1901 · 1 month ago

Adding Telugu as another data point on the same defect, plus a measurement that may help pin down what the width function should actually return.

Same environment: macOS (Darwin 25.5.0), default terminal, zsh.

Measuring three different notions of "length" over the same strings:

| string | code points | wcwidth sum | cluster count |
|---|--:|--:|--:|
| Hello world | 11 | 11 | 11 |
| నమస్కారం | 8 | 6 | 5 |
| Hello నమస్కారం | 14 | 12 | 11 |
| తెలుగు భాష | 10 | 8 | 6 |
| కృష్ణ | 5 | 4 | 3 |
| नमस्ते (Devanagari, for comparison) | 6 | 4 | 4 |

ASCII is the only row where all three agree. Every Telugu row disagrees by 1-3 columns, which matches the drift seen in padded or bordered output — the error accumulates per word, so a line mixing Telugu with English goes wrong in the middle rather than at the end.

Decomposition of నమస్కారం is Lo Lo Lo Mn Lo Mn Lo Mc. The Mn marks contribute 0 to a wcwidth sum but do participate in a rendered cluster, which is the mismatch you described.

One caveat worth flagging for whoever picks this up: grapheme-cluster count is closer but still not the rendered width for Indic scripts. UAX #29 extended grapheme clusters do not merge across a virama, so నమస్కారం segments into 5 clusters, while shaping shows a స్క conjunct and renders nearer 4 advances. So switching from wcwidth to UAX #29 segmentation fixes most of the drift but will not be exact for conjunct-heavy text. Terminals also differ in how they advance conjuncts, so the honest target may be "no worse than the terminal, and never overlapping" rather than an exact column count.

The cheapest reproduction that does not need a specific language is to render fixed-width padded rows and look at whether the right border stays straight — ASCII rows line up, Telugu rows drift by exactly the wcwidth-vs-cluster delta above.

JCSnap · 25 days ago

Here is another concrete reproduction showing how this collapses the TUI specifically inside the diff / edit preview view, causing cursor desync, ghost text, and prompt collision.

Context & Symptom

When viewing/editing a file with Tamil strings:

"cancelScheduled": "ரத்து செய்ய அட்டவணைப்பட்டது. உங்கள் காலம்..."
  1. Premature / broken line wrap: Combining characters cause string width to be miscalculated. The syllable கள் wraps onto an orphaned line (+கள் ",) beneath the diff.
  2. Vertical row desync & ghost overwrites: Because the terminal wraps earlier than the TUI internal row counter expects, relative cursor positioning (\x1b[NA) lands on the wrong row. Stale text from a previous buffer (y.test.ts)or-key.test.) is stamped directly across line 166.
  3. Prompt collision: The input prompt (> █ 3 import { checkoutErrorKey } ...) renders directly over the diff output instead of staying docked below.

Environment: macOS (Darwin arm64), Ghostty / default terminal.

Mnourkh01 · 25 days ago

Adding Arabic on Windows as another data point, with a byte-level finding that may help pin this down.

Environment: Claude Code 2.1.278, Windows 11 (build 26200), fullscreen renderer (the classic renderer shows the same fault), an xterm.js 6 based terminal over ConPTY. ConPTY passes the output through unchanged on this build, so the bytes below are Claude Code's own.

Minimal repro, no prompt needed:

  1. Open Claude Code in a 60 column terminal.
  2. Type, without sending, the word تغيّر eleven times separated by spaces. Each one carries a shadda (U+0651).
  3. The input box breaks: its second row starts about eleven cells to the right, and the lower separator and the status line are pushed out of place.
  4. Type the same word without the shadda (تغير) and the box draws correctly.

What the captured output shows: when a row is full, the renderer writes the next row straight after it with no CR LF and relies on the terminal's auto wrap. The width used for that decision (and for CSI n C cursor moves between cells) counts each Arabic mark U+064B to U+065F as one cell. The layout does not: a paragraph line of 92 code points holding two marks is emitted as one full row in a 90 column terminal. So any terminal that gives the marks zero width (wcwidth or grapheme clusters, including Windows Terminal) sees such a row one cell short per mark, the next row starts on the end of the previous one, and every row after it shifts. In a narrow or split pane this happens constantly, because more lines wrap.

Giving the marks one cell on the terminal side fixes the wrapped rows but breaks the full paragraph rows (one extra row each, the whole frame shifts), so no terminal setting can work around it. The two counts inside Claude Code need to agree, at zero width per mark (grapheme cluster width), which is also what the Tamil and Telugu reports above ask for.

Workaround in use for now: telling the model to write Arabic without tashkeel.

Showing cached comments. Read the full discussion on GitHub ↗