[BUG] Thai Combining Characters Not Rendered in Claude Code TUI
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
What's Wrong?
Thai vowel characters (Unicode combining characters) are completely invisible when typing or displaying in Claude Code CLI interactive mode. The base consonants appear, but all vowel marks that should appear above, below, or around them are missing.
Example:
- I type:
การอัปเดต(meaning "the update") - Claude Code displays:
กรอปเดต(missing vowel marksาandั)
This makes Thai language completely unusable in Claude Code, as the text becomes unreadable gibberish without the vowel marks.
Impact: Affects ~70M Thai speakers and likely all other languages using combining characters (Vietnamese, Arabic, Hebrew, Devanagari, etc.)
What Should Happen?
What Should Happen?
Thai text should display correctly with all combining characters (vowel marks and tone marks) visible, exactly like it does in standard terminals.
Expected display:
การอัปเดต ต่อจากที่ค้างไว้ ให้อ่านไฟล์ ตาม spec ที่ระบุว่า
What I see in Claude Code:
กรอปเดต ต่อจกที่ค้งไว้ ให้อ่นไฟล์ ตม spec ที่ระบุว่
Missing characters:
า(SARA AA, U+0E32) - appears after consonantsั(MAI HAN-AKAT, U+0E31) - appears above consonantsิีุู(other vowels, U+0E34-U+0E39)่้๊๋(tone marks, U+0E48-U+0E4B)
Error Messages/Logs
## Error Messages/Logs
No error messages are shown. The characters are silently dropped during rendering.
### Evidence that bytes are present but not rendered:
$ echo -n "การอัปเดต" | xxd -p
e0b881e0b8b2e0b8a3e0b8ade0b8b1e0b89be0b980e0b894e0b895
**Breakdown:**
e0b881 = ก (KO KAI) - base consonant ✅ DISPLAYED
e0b8b2 = า (SARA AA) - combining vowel ❌ NOT DISPLAYED (but bytes present!)
e0b8a3 = ร (RO RUA) - base consonant ✅ DISPLAYED
e0b8ad = อ (O ANG) - base consonant ✅ DISPLAYED
e0b8b1 = ั (MAI HAN-AKAT) - combining vowel ❌ NOT DISPLAYED (but bytes present!)
e0b89b = ป (PO PLA) - base consonant ✅ DISPLAYED
The Unicode bytes are correctly present in the data stream, but the TUI rendering engine is not displaying the combining characters.
### Comparison Test
**In iTerm2 (working correctly):**
$ echo "การอัปเดต"
การอัปเดต ← All characters visible
**In Claude Code (not working):**
claude> การอัปเดต
กรอปเดต ← Combining characters invisible
Steps to Reproduce
Steps to Reproduce
Minimal reproduction:
- Start Claude Code:
``bash``
claude
- Type Thai text with vowels:
````
การอัปเดต
- Observe the display:
- Base consonants:
ก,ร,อ,ป,เ,ด,ต✅ visible - Vowel marks:
า,ั❌ invisible
Full test case:
Test input:
กา กิ กี กุ กู เก แก โก ใก ไก
ก็ ก้า ก๊า ก๋า กำ
การอัปเดต ต่อจากที่ค้างไว้
Expected output (what iTerm2 shows):
กา กิ กี กุ กู เก แก โก ใก ไก
ก็ ก้า ก๊า ก๋า กำ
การอัปเดต ต่อจากที่ค้างไว้
Actual output in Claude Code:
ก กิ กี กุ กู เก แก โก ใก ไก
ก ก้ ก๊ ก๋ กำ
กรอปเดต ต่อจกที่ค้งไว้
Environment Details
- OS: macOS Darwin 25.2.0
- Claude Code Version: 2.1.23
- Terminal: Ghostty (also tested with iTerm2)
- Shell: zsh
- Font: SF Mono (also tested: Menlo, Monaco, JetBrains Mono, MesloLGS Nerd Font)
- Locale:
en_US.UTF-8(also tested withth_TH.UTF-8)
Verification that it's Claude Code specific:
| Terminal | Thai Display | Status |
|----------|--------------|--------|
| iTerm2 (without Claude Code) | การอัปเดต | ✅ Works perfectly |
| macOS Terminal (without Claude Code) | การอัปเดต | ✅ Works perfectly |
| VSCode Terminal (without Claude Code) | การอัปเดต | ✅ Works perfectly |
| Ghostty (without Claude Code) | การอัปเดต | ✅ Works perfectly |
| Claude Code in ANY terminal | กรอปเดต | ❌ Broken |
Conclusion: Same font, same locale, same terminal emulator → works fine WITHOUT Claude Code, breaks WITH Claude Code.
What I tried (all failed):
- ✅ Changed terminal emulator: Ghostty → iTerm2 → macOS Terminal
- ✅ Changed fonts: SF Mono, Menlo, Monaco, JetBrains Mono, Noto Sans Thai
- ✅ Changed locale:
LANG=th_TH.UTF-8,LC_ALL=th_TH.UTF-8 - ✅ Verified Unicode normalization (text is NFC form)
- ✅ Updated Claude Code to latest version (2.1.23)
None of these fixed the issue.
Claude Model
Sonnet (default)
Is this a regression?
Yes, this worked in a previous version
Last Working Version
_No response_
Claude Code Version
2.1.23
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
iTerm2
Additional Information
Technical Analysis
Root Cause (Suspected)
The TUI library used by Claude Code likely does not handle grapheme clusters correctly.
Thai script uses combining characters that must be rendered together with their base character as a single visual unit:
- Base character:
ก(consonant) - Combining character:
า(vowel mark) - Grapheme cluster:
กา(one visual unit)
The TUI is probably:
- Treating each Unicode codepoint as separate character
- Not invoking font shaping engine (HarfBuzz/Core Text)
- Dropping combining characters during width calculation
Affected Unicode Ranges (Thai)
| Range | Description | Status |
|-------|-------------|--------|
| U+0E01-U+0E30 | Base consonants | ✅ Displayed |
| U+0E31 | MAI HAN-AKAT (ั) | ❌ Not displayed |
| U+0E32 | SARA AA (า) | ❌ Not displayed |
| U+0E34-U+0E39 | Vowels above/below | ❌ Not displayed |
| U+0E47-U+0E4E | Tone marks | ❌ Not displayed |
Similar Issues in Other Languages
This likely affects:
- 🇻🇳 Vietnamese (combining tone marks)
- 🇸🇦 Arabic (vowel diacritics: ً ٌ ٍ َ ُ ِ)
- 🇮🇱 Hebrew (nikud marks: ְ ֱ ֲ ֳ)
- 🇮🇳 Devanagari (matras: ि ी ु ू)
- 🇲🇲 Myanmar
- 🇰🇭 Khmer
Suggested Fix
The TUI rendering pipeline should:
- Use grapheme cluster segmentation instead of codepoint iteration
- Rust crate:
unicode-segmentation - Example:
"กา".graphemes(true)→["กา"]not["ก", "า"]
- Enable font shaping via platform APIs:
- macOS: Core Text (CTLineCreateWithAttributedString)
- Linux: HarfBuzz
- Windows: DirectWrite
- Calculate display width using grapheme clusters:
unicode-widthcrate with grapheme-aware mode"กา".width()should return 1, not 2
- Preserve combining characters during rendering:
- Don't strip codepoints in U+0300-U+036F, U+0E31-U+0E3A, U+0E47-U+0E4E
Example Fix (Pseudocode)
// Current (broken):
for codepoint in text.chars() {
render_single_char(codepoint); // ❌ Loses combining chars
}
// Fixed:
for grapheme in text.graphemes(true) {
render_grapheme_cluster(grapheme); // ✅ Preserves combining chars
}
---
Workaround
Until this is fixed, Thai users must:
- Write prompts in English
- Compose Thai text in external editor (iTerm2/VSCode) and copy-paste to Claude Code
- Use separate terminal window for Thai input
---
Additional Context
- Thai is the official language of Thailand with ~70M speakers
- Critical for Southeast Asian market adoption
- Affects professional developers who need to document in native language
- Similar rendering works perfectly in: VS Code, Cursor, GitHub Copilot Chat
---
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗