[BUG] Korean (Hangul) text is garbled/corrupted in AskUserQuestion and TodoWrite card UI (VSCode extension)

Status Open
Reported on v2.1.218
Maintainer reply None cached
Activity 7 comments · opened Jul 23, 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?

Description

Korean text renders correctly in normal chat messages, but gets visibly corrupted specifically inside two structured card-style UI components:

  1. AskUserQuestion (the multiple-choice question card) — header chip text and option labels/descriptions render as garbled/incorrect Hangul syllables.
  2. 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

  1. Have Claude Code call the AskUserQuestion tool with Korean text in the header/question/option fields
  2. Observe the rendered card in the VSCode extension UI
  3. Compare against the Korean text Claude actually sent (visible in the same session's plain chat text, which renders correctly)
  4. Separately, have Claude Code call TodoWrite with 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_

View original on GitHub ↗

5 Comments

te6-in · 1 month ago

Reproducible @ 2.1.220 (macOS 15.7.7), for me it's been working like this for some weeks actually

shinsungkim · 1 month ago

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 an assistant entry's tool_use.input — the payload as recorded by the CLI, not the rendered card:

L371 [assistant] tool_use TodoWrite  "라이드 검증 사용자 생다(...)"        <- 전달 → 생다
L410 [assistant] tool_use TodoWrite  "... 재배포 @NN 라이밍 (롤백 ...)"    <- 라이브 → 라이밍
L410 [assistant] tool_use TodoWrite  "밌랜치 커밋 완료 / push ..."         <- 브랜치 → 밌랜치

(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 preview field 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

TodoWrite rewrites 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 Code VSCode extension, macOS 15 (Darwin 25.1.0) — this issue reports Windows 11, so the behavior is not OS-specific
  • Model where observed: claude-opus-5 (including the 1M-context variant)
  • Workaround that appears to help: switching the session to a different model (/model sonnet)
shinsungkim · 1 month ago

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_use input, before any UI gets involved.

Environment

  • Claude Code, VSCode extension, macOS
  • Model: Opus 5 (1M context)
  • Long-running Korean-language session (many turns)

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 at tool_use blocks:

import json
bad = ["후킹", "새람", "샘지어"]   # corrupted syllables seen in the rendered card
for line in open(SESSION_JSONL, encoding="utf-8"):
    rec = json.loads(line)
    msg = rec.get("message") or {}
    for c in (msg.get("content") or []):
        if isinstance(c, dict) and c.get("type") == "tool_use":
            s = json.dumps(c.get("input", {}), ensure_ascii=False)
            for b in bad:
                if b in s:
                    print(c["name"], b)

Result: the corrupted strings are found verbatim inside the AskUserQuestion tool 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:model so 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.

shinsungkim · 1 month ago

Still reproducing on claude-opus-5 (1M context), this time in AskUserQuestion option label / description fields.

Evidence it is generation-side, not rendering: the corrupted syllables are already present in the session transcript's tool_use input payload (~/.claude/projects/<project>/<session>.jsonl, type: assistantcontent[].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:

  • Valid Hangul syllables substituted with other valid syllables (not mojibake, not ?), so it survives every encoding check.
  • Concentrated in longer Korean prose inside tool inputs (option descriptions, todo labels) rather than in the assistant's visible message text in the same turn.
  • Appears later in long sessions.

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 sonnet or opus-4-8 for the session, and keep tool-input strings short (put long explanation in the assistant message instead of inside tool inputs).

shinsungkim · 1 month ago

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 AskUserQuestion call; the rest in TodoWrite.

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: TodoWrite and AskUserQuestion payloads were corrupted, but Write/Edit payloads emitting several thousand words of Korean prose were not. I checked this rather than eyeballing it:

  • Built a Hangul syllable inventory from 885 pre-existing Korean documents in the same repository (1,429 unique syllables), then diffed it against the two files this session produced (one HTML, one Markdown). Result: 1 novel syllable, and it was legitimate (나눕니다, a normal inflection).
  • Repeated at word level against a 173k-word corpus from the same repository: every novel 3+ syllable word was a legitimate new phrasing.

So in this session the corruption was confined to the structured tool-input path and did not reach Write/Edit content. 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_use input 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 sonnet or opus-4-8, and keep tool-input strings short by putting long explanation in the assistant message rather than inside tool inputs.

Showing cached comments. Read the full discussion on GitHub ↗