claude-utf8-guard

Add "점검도 완료된" to the todo list

☐ 점검도 완료된

not 점깔도 완릷된

UTF-8 Guard for Claude

Stops Korean, Japanese and Chinese in Claude Code's tool calls from turning into wrong characters. Claude sometimes writes them as \uXXXX escapes and gets a hex digit wrong; this plugin tells it to write UTF-8 and holds back a call whose text looks broken.

Without it, the todo above can read 점깔도 완릷된: real Hangul, wrong syllables.

Install

At the Claude Code terminal prompt:

/plugin install utf8-guard --marketplace M1nu0x0/claude-utf8-guard

Answer y to Add marketplace? and choose the user scope. Node.js 18 or later must be on your PATH.

From a shell:

claude plugin install utf8-guard --marketplace M1nu0x0/claude-utf8-guard

In Claude Desktop, click + next to the prompt box → Plugins → Add marketplace, enter M1nu0x0/claude-utf8-guard, then add utf8-guard. It applies from the next session.

Before and after

tool_use TodoWrite {"content": "\uc810\uae54\ub3c4 \uc644\ub9b7\ub41c"}
card  ☐ 점깔도 완릷된
Written as escapes: two syllables come out wrong
tool_use TodoWrite {"content": "점검도 완료된"}
card  ☐ 점검도 완료된
Written in UTF-8, as this plugin asks

Why

In a tool call such as AskUserQuestion, TodoWrite or a Notion or Jira MCP tool, Claude can write Korean as \uXXXX escapes instead of the characters. When it spells a hex digit wrong, the escape still decodes to a real syllable, so nothing looks broken until someone reads it.

anthropics/claude-code#83033 ran it both ways: every run that wrote escapes (45 of 45) broke some of its Hangul, about 3 to 5% of the syllables, and runs told to write UTF-8 almost never did. Claude Code decodes the escapes faithfully, so it cannot repair a wrong code point; the issue went to the model team.

Japanese is affected too. #54051 reported 揃え written as 諿え, 処分 as 処勭 and 側 as the simplified Chinese 侧 in Notion MCP calls, and #59649 reported 叩き as 鯰し and 再起動 as 在控 in AskUserQuestion. Whether escapes caused these is not confirmed, so the plugin checks the result instead.

Once that issue is closed as fixed, this plugin is no longer needed. It tells you so at the start of a session, and CI checks the issue every day.

How it works

At session start a SessionStart hook adds one instruction for Claude: write every non-ASCII character in a tool call as itself in UTF-8, never as a \uXXXX escape, except an escape that is part of code it is copying. This is the workaround #83033 verified.

Before each tool call a PreToolUse hook looks at the text in its input. If something looks broken it holds the call back once and tells Claude which characters and where; the same call sent again unchanged goes through, so a rare word you meant costs one round trip and never blocks the work.

Once a day the session-start hook asks GitHub, in the background, whether #83033 is closed as fixed. When it is, you see a line at the start of a session saying you can turn the plugin off. The notice and the Korean and Japanese checks can be turned off in /config.

What it checks

Claude Code decodes the model's JSON before a hook sees it, so the hook never sees the escape itself, only what it turned into:

Rare Hangul
A syllable outside the 2,350 of KS X 1001 (EUC-KR), which cover almost all Korean text. One wrong hex digit lands outside them about 70% of the time: 릷, 딅 and 잩 from #83033 are caught.
Rare kanji
In text with kana, a kanji outside the 6,355 of JIS X 0208 (Shift_JIS), such as 諿, 勭 or the simplified 侧 and 亿 from #54051. A wrong hex digit lands outside them about 66% of the time. Text with no kana may be Chinese and is not checked.
Half a surrogate pair
A lone \uD83A and the like, which the API rejects with a 400 that can end the session.
U+FFFD
The replacement character, left where something failed to decode.
\uXXXX
An escape for Hangul, kana or CJK written out as text in a question, todo or MCP call, outside code.

It cannot catch a wrong character that is itself common: a wrong digit that lands on another common syllable (잡히지 as 잡힐지), or a kanji such as 鳥 for 鳴 or 在控 for 再起動. Nor can it catch a call Claude Code rejects before any hook runs, or wrong hanzi in Chinese text.

Related issues

Badges show each issue's current state.

The cause

#83033
Claude writes Korean tool-call parameters as \uXXXX escapes and spells the hex wrong. The reason for this plugin.

The same symptom

#80009
Corrupted Korean syllables in AskUserQuestion input, confirmed in the session log.
#78996
Long Korean sessions degrade into ill-formed Hangul.
#57981
Wrong but valid Korean syllables in AskUserQuestion, now and then.
#53665
Korean garbled in tool_use JSON parameters on Opus 4.7.
#82588
Opus 5 writes wrong Hangul syllables.
#79339
A glitch in the middle of an escape makes the whole tool input unparsable.
#61670
A lone \uD83A surrogate with garbled Korean ends the session.
#69522
Long escaped tool arguments fail to parse.
#54051
Wrong kanji, some of them simplified Chinese, in Japanese written through Notion MCP tools.
#59649
Similar-looking but wrong kanji in AskUserQuestion on Opus 4.7.
#56003
Japanese shown as raw \uXXXX in AskUserQuestion.
#78568
Wrong characters in Traditional Chinese output.

The same escapes, from a tool

#99361
After an escaped match, Edit writes every non-ASCII character in new_string as an escape.
#72957
Write and Edit decode \uXXXX in file content.

What a hook cannot see

#93311
A call rejected at input validation never reaches PreToolUse.
#96285
Hook stdin is UTF-8; Python hooks on Windows read it as ANSI. This plugin reads it as UTF-8.

Questions

Does it change what Claude writes?

Only through the instruction at session start, which Claude may not always follow. The hooks never rewrite a call: they let it through, or hold it back once and say why.

I write Japanese or Chinese. Does it help?

Yes for Japanese: the instruction, the kanji check and the surrogate, U+FFFD and literal-escape checks all apply. For Chinese only the instruction and those last three apply: hanzi outside a fixed set are normal in Chinese, so a wrong one cannot be told apart.

What if a rare syllable is what I meant?

Claude sees why the call was held back and sends it again unchanged, and the second time it goes through. If it happens often, turn off "Check Korean syllables" or "Check Japanese kanji" in /config.

Does it send data anywhere?

Once a day, if "Notify when fixed upstream" is on, it asks the GitHub API for the state of anthropics/claude-code#83033 and keeps the answer in the plugin's data folder. Nothing from your sessions is sent.

Which systems is it tested on?

CI runs on Ubuntu, Windows and macOS, and every day against the latest Claude Code. The hooks need Node.js 18 or later.

How do I remove it?

Run claude plugin disable utf8-guard to turn it off, or claude plugin uninstall utf8-guard to remove it.