[BUG] Korean (Hangul) text is garbled/corrupted in AskUserQuestion and TodoWrite card UI (VSCode extension)
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?
Description
Korean text renders correctly in normal chat messages, but gets visibly corrupted specifically inside two structured card-style UI components:
- AskUserQuestion (the multiple-choice question card) — header chip text and option labels/descriptions render as garbled/incorrect Hangul syllables.
- TodoWrite ("Update Todos" card) — same category of corruption appears to affect the todo list display as well.
Regular assistant chat text and user chat input containing the exact same Korean strings display correctly. The corruption is isolated to these two structured/card UI components.
Example
Original text I asked Claude to render in an AskUserQuestion option:
- Header:
깜빡 위치 - Question:
한국어가 깨지는 게 어디인가요?
What actually rendered in the UI (see attached screenshot):
- Header:
김침 위쎏 - Question:
한구어가 김겨지는 거이 어마지인가요?
Note the corrupted output is still valid-looking Hangul syllables (not mojibake/boxes/question marks) — syllable structure is preserved but individual jamo appear swapped/substituted. Looks like a Unicode normalization (NFC/NFD) or jamo-composition bug rather than a plain encoding/charset mismatch.
Environment
- Claude Code running as a VSCode native extension
- OS: Windows 11 Pro
- Console/terminal codepage confirmed UTF-8 (
chcp→ 65001,[Console]::OutputEncoding→ utf-8) — ruled out as the cause - Regular chat text (both directions) renders Korean correctly throughout the same session
Steps to reproduce
- Have Claude Code call the
AskUserQuestiontool with Korean text in the header/question/option fields - Observe the rendered card in the VSCode extension UI
- Compare against the Korean text Claude actually sent (visible in the same session's plain chat text, which renders correctly)
- Separately, have Claude Code call
TodoWritewith Korean todo content and observe the same category of corruption in the "Update Todos" card
Expected behavior
Korean text in AskUserQuestion and TodoWrite cards should render identically to normal chat messages.
Actual behavior
Korean text in these two card components is corrupted into different (but still Hangul-shaped) syllables.
<img width="837" height="349" alt="Image" src="https://github.com/user-attachments/assets/8a9d65b1-6552-4a7f-b13e-d80a464eed2e" />
What Should Happen?
.
Error Messages/Logs
Steps to Reproduce
.
Claude Model
None
Is this a regression?
Yes, this worked in a previous version
Last Working Version
_No response_
Claude Code Version
Claude Code VSCode extension version: 2.1.218
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Terminal.app (macOS)
Additional Information
_No response_
Showing cached comments. Read the full discussion on GitHub ↗
5 Comments
Reproducible @ 2.1.220 (macOS 15.7.7), for me it's been working like this for some weeks actually
Same symptom here, but I have evidence that this is not a UI rendering bug. The corruption is already present in the on-disk session log, i.e. before anything is rendered.
Corruption exists in the session JSONL, not just the UI
I grepped the corrupted strings in
~/.claude/projects/<project>/<session>.jsonl. Every hit is inside anassistantentry'stool_use.input— the payload as recorded by the CLI, not the rendered card:(identifiers redacted; the corrupted Korean tokens are verbatim)
So the substitution happens at generation time or during response decoding — the card UI is faithfully displaying a payload that is already wrong. Worth re-checking the premise in this issue that "regular chat text is correct": in my sessions plain chat text was mostly clean too, but that may just be because these short structured labels are where the corruption concentrates, not because the UI layer is at fault.
Also relevant to #75975: in my case the
previewfield of AskUserQuestion was corrupted too, so "preview is unaffected" does not hold generally.Observed substitutions (Korean word → corrupted form)
| intended | rendered | meaning |
|---|---|---|
| 재빌드 | 재뱠드 | rebuild |
| 취소 | 초소 | cancel (also 주문취소율 → 주문초소율) |
| 마크업 | 마쪩업 | markup |
| 브랜치 | 밌랜치 | branch |
| 라이브 | 라이밍 | live |
| 전달 | 생다 | hand over |
| 본문 | 본밎 | body text |
| 문구가 | 및구가 | wording |
| 테이블 | 테이및 | table |
| 펼치면 | 핌치면 | expand |
| 토글 | 토톥 | toggle |
These are whole-syllable substitutions into other valid Hangul syllables, consistent with what this issue describes.
Model correlation
I scanned my 25 most recent sessions and counted confirmed corruptions against assistant turns per model:
| model | confirmed corruptions | assistant turns |
|---|---|---|
| claude-opus-5 | 60 | 1,289 |
| claude-sonnet-5 | 0 | 1,326 |
| claude-opus-4-8 | 0 | 196 |
| claude-fable-5 | 0 | 149 |
| claude-haiku-4-5 | 0 | 90 |
Sonnet 5 had more assistant turns than Opus 5 with zero hits. Caveat on method: my match list was built from words I found corrupted in Opus 5 sessions, so there is selection bias — a different model could corrupt different words that my list would miss. I tried a dictionary-free check (flagging rare-syllable words) but it had no discriminative power, since it flags many legitimate Korean words. Still, the only actually-malformed forms in that output appeared in Opus 5 sessions.
Why it looks persistent to the user
TodoWriterewrites the entire todo list on every update. Once one label is corrupted, it is copied verbatim into every subsequent list update, so a handful of corruptions surface repeatedly and read as "it keeps happening".This also has a real correctness cost, not just cosmetic: in one AskUserQuestion card the corrupted word was a domain term (취소 "cancel" → 초소), so the option descriptions the user was asked to choose between contained wrong terminology.
Environment
claude-opus-5(including the 1M-context variant)/model sonnet)Still reproducing today (2026-07-29/30). Adding a data point that I think changes the diagnosis: this is not a rendering bug in the card components — the corruption is already present in the model-generated
tool_useinput, before any UI gets involved.Environment
Evidence that it is not a rendering issue
I grepped the raw session transcript (
~/.claude/projects/<slug>/<session>.jsonl) for the corrupted syllables that appeared on screen, looking only attool_useblocks:Result: the corrupted strings are found verbatim inside the
AskUserQuestiontool input as persisted on disk. So the bad syllables were emitted by the model into the tool parameters; the card UI is faithfully displaying what it was given.(The only other hits were my own grep script quoting the same strings — not additional occurrences.)
Samples from this session (intended → actually emitted)
| intended | emitted |
|---|---|
| 막고 | 말고 |
| 이미 | 이밋 |
| 저촉 | 샘지어 |
| 등록 | 뒁록 |
| 훅이 | 후킹이 |
Note the failure mode: these are not mojibake / replacement characters. They are well-formed but wrong Hangul syllables, and some are real words with a different meaning (막고 "blocking" → 말고 "not that one"), so the sentence stays grammatical while the meaning flips. That makes it easy to miss and actively misleading in a decision prompt — in my case the corrupted text was in the options of a question asking the user to choose how to proceed.
Possible regression
#53665 ("Opus 4.7: Korean text garbling in tool_use JSON parameters (AskUserQuestion)") described exactly this mechanism and was auto-closed as stale. #78996 tracks the broader "sustained Korean sessions degrade into ill-formed Hangul" pattern. Given the transcript evidence above, this issue looks like the same model-side problem rather than a VSCode-extension display bug, and may be worth re-labeling
area:modelso it isn't triaged as a UI fix.Scope check I ran
I verified that no corrupted text leaked into the files this session wrote (markdown notes and a worklog were clean); the corruption appeared only in the interactive question card. But since the same mechanism affects any tool parameter, corrupted text reaching written artifacts seems possible in principle.
Still reproducing on claude-opus-5 (1M context), this time in
AskUserQuestionoptionlabel/descriptionfields.Evidence it is generation-side, not rendering: the corrupted syllables are already present in the session transcript's
tool_useinput payload (~/.claude/projects/<project>/<session>.jsonl,type: assistant→content[].type: tool_use,name: AskUserQuestion). Grep of the raw JSONL finds the broken strings verbatim, so nothing downstream (terminal, font, IDE) is involved.Samples from one turn (Korean, intended → produced):
| intended | produced |
|---|---|
| 마스터매칭 (master-matching) | 마스턴매칭 |
| 읍/면/동 (administrative unit suffixes) | 읽/면/동 |
| 양지읍 (a place name) | 양지읽 |
| 씁니다 (uses) | 썹니다 |
| 승격 (promotion/upgrade) | 승경 |
| 가깝지만 (close, but) | 가입지만 |
Pattern consistent with earlier reports in this thread:
?), so it survives every encoding check.Why this one is costly: the corrupted text lands in a decision prompt. Domain terms and place names were mangled inside the options a user had to choose between, so the user is asked to approve a plan whose description they cannot fully trust. In this case it hit an administrative-region matching rule where
읍vs면is a meaningful distinction, and the corruption changed exactly those characters.Workaround that holds so far: switch to
sonnetoropus-4-8for the session, and keep tool-input strings short (put long explanation in the assistant message instead of inside tool inputs).Another occurrence today on claude-opus-5 (1M context), VSCode extension, Claude Code 2.1.42. Two data points I don't think are in this thread yet.
1. Deletion, not only substitution
Earlier samples in this thread were all syllable→syllable substitutions. This session also produced dropped syllables:
| intended | produced | shape |
|---|---|---|
| 도메인 (domain) | 도서인 | syllable substituted |
| 성격 (character/nature) | 성겁 | final consonant substituted, ㄱ → ㅂ |
| 대신 (instead) | 대슸 | final consonant substituted, ㄴ → ㅆ |
| 단순화 (simplification) | 단수화 | final consonant dropped |
| 빠짐없이 (without omission) | 븏짐없이 | syllable substituted |
| 돌림판으로 (…by rotation) | 돌림판으 | trailing syllable dropped |
Five of these landed in a single
AskUserQuestioncall; the rest inTodoWrite.Note
븏and슸. Both are valid Unicode Hangul but essentially never appear in modern written Korean. Combined with the outright deletions, this looks less like "sampled a nearby plausible word" and more like something in Hangul token handling on this path.2. Negative control: file-writing tools in the same session were clean
Same session, same model, overlapping turns:
TodoWriteandAskUserQuestionpayloads were corrupted, butWrite/Editpayloads emitting several thousand words of Korean prose were not. I checked this rather than eyeballing it:나눕니다, a normal inflection).So in this session the corruption was confined to the structured tool-input path and did not reach
Write/Editcontent. If that separation holds generally, it narrows the surface considerably: the same model in the same session emitted long correct Korean through one tool path and corrupted Korean through another.Detection recipe for others in this thread
To check whether your delivered files were hit (as opposed to just the UI), diff the Hangul syllable set of the new file against a corpus of your existing Korean documents. Corruption tends to introduce syllables that essentially never occur in normal text, so novel-syllable count is a cheap detector.
Caveat: it will not catch corruptions composed only of common syllables.
단순화→단수화passes that filter, since 단/수/화 are all ordinary. So pair it with a word-level diff against the same corpus and read the novel-word list.Confirming the earlier diagnosis
As in my previous comments, the corrupted syllables are already present in the session transcript's
tool_useinput payload, so nothing downstream (terminal, font, IDE) is involved. Grepping the raw JSONL finds the broken strings verbatim.Workaround still holding: switch the session to
sonnetoropus-4-8, and keep tool-input strings short by putting long explanation in the assistant message rather than inside tool inputs.