[BUG] Backspace deletes an entire grapheme cluster, wiping a whole Thai syllable in one press (v2.1.220)

Status Open
Reported on v2.1.220
Maintainer reply None cached
Activity 1 comment · opened Aug 3, 2026

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?

In the interactive prompt, one backspace deletes a whole grapheme cluster. For Thai that removes an entire syllable rather than a single character, so a typo cannot be corrected without retyping the syllable.

Typing น้ำ (water) and pressing backspace once leaves the input completely empty.

Every native macOS text field, and Thai text entry generally, deletes one codepoint per backspace: น้ำน้.

Ctrl+H behaves identically.

This is not the same as the existing Thai issues

The distinction matters because these point at different code:

  • #81183 (Thai combining-mark cursor bug, v2.1.220) reports marks being dropped/misplaced and the cursor landing wrong, and diagnoses the input as tracking codepoint count rather than grapheme clusters. On 2.1.220 I measured the opposite: navigation is grapheme-cluster based, and cell widths are correct.
  • #22390 fixed SARA AA being misclassified as zero-width. Consistent with what I see now: SARA AM (U+0E33) is likewise a spacing character and its width is handled correctly.
  • #82338 (Tamil combining-mark width) and #21149 (Thai output drops vowels) are rendering/width issues.

This report is purely about delete granularity, with widths and rendering measured as correct.

Evidence

Measured by driving the real TUI in a pty with a pyte screen model, reading actual screen state rather than scraping the byte stream (a minimal redraw re-emits nothing, so naive stream scraping reports false negatives):

after typing น้ำ    : 'น้ำ' = U+0E19 + U+0E49 + U+0E33
after backspace x1  : (no Thai on screen)
after backspace x2  : (no Thai on screen)
after backspace x3  : (no Thai on screen)

Same harness against nano, showing the expected behaviour:

typed        : น + ้ + ำ
backspace x1 : 'น้'   <- only ำ removed

Terminal ruled out: both Apple Terminal 470.2 and Ghostty 1.3.1 report correct cell widths for 11 Thai test cases, including the right-margin wrap of กั้, measured with DSR cursor-position queries.

Likely cause

The prompt buffer navigates by grapheme cluster:

getGraphemeBoundaries(){
  ...
  for (let {index:e} of wT().segment(this.text)) this.graphemeBoundaries.push(e);
  ...
}
prevOffset(e){
  if (e <= 0) return 0;
  return this.withCache(`prev:${e}`, () => {
    let t = this.getGraphemeBoundaries();
    return this.binarySearchBoundary(t, e, false);
  });
}

wT() is new Intl.Segmenter(void 0, {granularity:"grapheme"}), over text normalized to NFC. ICU folds a Thai syllable into a single grapheme, so prevOffset jumps past the whole syllable.

Related detail: Intl.Segmenter also folds (U+0E33) into the preceding cluster even though it is category Lo and occupies its own cell, so cluster count and display width diverge for the very common words (น้ำ, ทำ, คำ, สำหรับ).

Impact

Any script that stacks combining marks and whose users expect codepoint-wise deletion: Thai, Lao, Khmer, Myanmar, and to varying degrees Devanagari, Bengali, Tamil, Arabic with harakat, and Hebrew with niqqud.

For Thai users this makes the prompt painful for everyday use: deleting one wrong tone mark means retyping the whole syllable.

Note on what is NOT broken

Ctrl+W handles Thai correctly and should be left alone. getWordBoundaries() uses Intl.Segmenter with word granularity, which does dictionary-based Thai segmentation:

typed        สวัสดีครับผมชื่อหนึ่ง
after ctrl+w สวัสดีครับผมชื่อ      <- deleted exactly หนึ่ง, one Thai word

What Should Happen?

Backspace should delete one codepoint, not one grapheme cluster, at least for scripts where that is the platform convention. น้ำ + backspace should leave น้.

A blanket switch to codepoint deletion would regress emoji, since ZWJ sequences like 👨‍👩‍👧 should stay atomic. So the fix likely needs to be script-aware, or exposed as a setting, for example backspaceGranularity: "grapheme" | "codepoint".

Worth noting there is currently no delete or backspace action in the Chat keybinding context, so users cannot work around this by rebinding. The only available workaround is ctrl+g into $EDITOR.

Steps to Reproduce

  1. Run claude interactively
  2. At the prompt, type น้ำ (U+0E19 NO NU, U+0E49 MAI THO, U+0E33 SARA AM)
  3. Press backspace once
  4. Observe the input is now completely empty, instead of showing น้

Reproduces in both Apple Terminal 470.2 and Ghostty 1.3.1, so it is not terminal-specific. Locale en_US.UTF-8.

Claude Model

Opus

Is this a regression?

I don't know

Claude Code Version

2.1.220 (Claude Code)

Platform

Anthropic API

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗