Claude Codeの日本語文字化け�をHooksで自動検出・ブロックする
はじめに
Claude Code でコードや文章を書かせていると、日本語などの非ASCII文字が壊れることがあります。具体的には、本来の文字が Unicode 置換文字 U+FFFD に化けてファイルに書き込まれる現象です。
この記事では、Claude Code の Hooks 機能を使って、ファイル書き込み直後に文字化けを自動検出し、やり直しを指示する仕組みを紹介します。
問題: U+FFFD の混入
Claude Code の Write や Edit ツールで日本語を含むファイルを書き込んだとき、まれに以下のような文字化けが発生します。
期待: Claude Codeは便利なツールだ
実際: Claude Code��便利なツール�
U+FFFD(REPLACEMENT CHARACTER)は、UTF-8 としてデコードできないバイト列に遭遇したときに置換される文字です。この文字がファイルに混入すると、本来の文字情報は失われています。
手動で気づいて直せればいいですが、長いファイルの途中に紛れ込むと見落としやすいですよね。自動で検出してブロックできれば理想的です。
解決策: Hooks による自動検出
Claude Code の Hooks は、ツール実行のライフサイクルに介入できる仕組みです。シェルスクリプトなどを登録しておくと、特定のイベント発生時に自動で実行されます。
今回は PostToolUse フックを使います。Write や Edit でファイルが書き込まれた直後に検査スクリプトを実行し、U+FFFD が含まれていたらブロックして Claude Code にやり直しを指示します。
全体の流れ
Claude Code が Write/Edit を実行
↓
ファイルが書き込まれる
↓
PostToolUse フックが発火
↓
check-garbled-utf8.sh が実行される
↓
ファイル内に U+FFFD があるか検査
↓
なし → 正常終了
あり → JSON で block を返す → Claude Code がやり直し
設定方法
1. フックスクリプトを作成する
~/.claude/hooks/check-garbled-utf8.sh を作成します。
#!/bin/bash
# PostToolUse hook: Write/Edit 後にファイルの UTF-8 破損文字 (U+FFFD) を検出する
f=$(jq -r '.tool_input.file_path // .tool_response.filePath // empty')
if [ -n "$f" ] && [ -f "$f" ] && LC_ALL=C grep -l $'\xef\xbf\xbd' "$f" >/dev/null 2>&1; then
echo "{\"decision\":\"block\",\"reason\":\"UTF-8 broken chars detected in $f. Fix the garbled text.\"}"
fi
実行権限を付与します。
chmod +x ~/.claude/hooks/check-garbled-utf8.sh
スクリプトの処理を順に解説します。
入力の受け取り
フックスクリプトは stdin から JSON を受け取ります。PostToolUse の場合、以下のような構造です。
{
"session_id": "abc123",
"cwd": "/path/to/project",
"hook_event_name": "PostToolUse",
"tool_name": "Write",
"tool_input": {
"file_path": "/path/to/file.md",
"content": "ファイルの内容..."
}
}
jq で tool_input.file_path を取り出しています。tool_response.filePath へのフォールバックも入れてあります。
U+FFFD の検出
U+FFFD の UTF-8 バイト列は \xef\xbf\xbd(3バイト)です。LC_ALL=C を設定してロケールに依存しないバイト列マッチングを行い、grep でこのバイト列を検索します。
ブロック
U+FFFD が見つかった場合、JSON で decision: "block" と reason を stdout に出力します。Claude Code はこの応答を受け取ると、書き込みをエラーとして扱い、文字化けを修正して再試行します。
見つからなければ何も出力せずに正常終了(exit 0)します。
2. settings.json にフックを登録する
~/.claude/settings.json に以下を追加します。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/check-garbled-utf8.sh",
"timeout": 5
}
]
}
]
}
}
各フィールドの意味は以下の通りです。
| フィールド | 値 | 説明 |
|---|---|---|
PostToolUse |
- | ツール実行後に発火するイベント |
matcher |
"Write|Edit" |
対象ツール名の正規表現。パイプで OR 指定 |
type |
"command" |
シェルコマンドとして実行 |
command |
スクリプトのパス | 実行するスクリプト |
timeout |
5 |
タイムアウト(秒) |
matcher を Write|Edit にすることで、ファイル書き込みを行うツールだけにフックを限定しています。Bash や Grep など無関係なツールでは発火しません。
Hooks の仕組み
ここで Hooks の全体像を整理しておきましょう。
フックイベント
Hooks では、以下のライフサイクルイベントにスクリプトを登録できます。
| イベント | タイミング | 用途 |
|---|---|---|
PreToolUse |
ツール実行前 | 入力の検証、実行のブロック |
PostToolUse |
ツール実行後(成功時) | 出力の検証、後処理 |
Notification |
通知発生時 | 外部通知連携 |
Stop |
Claude が応答を完了した時 | 最終チェック、ログ記録 |
今回の文字化け検出には PostToolUse が適しています。ファイルが実際に書き込まれた後でないと内容を検査できないためです。
フックの種類
| 種類 | 説明 |
|---|---|
command |
シェルコマンドを実行します。stdin で JSON を受け取り、stdout/stderr + 終了コードで応答します |
今回は command 型のフックを使っています。
応答の仕組み
フックスクリプトは stdout に JSON を出力することで Claude Code にフィードバックを返せます。
{"decision": "block", "reason": "エラーの説明"}
-
decision: "block"を返すと、Claude Code はそのツール実行を失敗として扱い、reason の内容をもとにリトライします - 何も出力せず exit 0 で終了すれば、正常として処理が続行されます
設定の配置場所
| ファイル | スコープ |
|---|---|
~/.claude/settings.json |
全プロジェクト共通(個人設定) |
.claude/settings.json(プロジェクトルート) |
プロジェクト固有(チーム共有可能) |
.claude/settings.local.json |
プロジェクト固有(ローカルのみ) |
UTF-8 の文字化け検出は言語を問わず有用なので、~/.claude/settings.json にグローバルに設定するのがおすすめです。
動作確認
Claude Code 上で Write や Edit を使って日本語を含むファイルを書き込ませ、ログにフックの実行が表示されることを確認します。
文字化けが発生しなければフックは何も出力せず正常終了します。意図的に文字化けを再現するのは難しいですが、フック自体が動いているかは以下のコマンドでテストできます。
# U+FFFD を含むテストファイルを作成
echo -e "テスト\xef\xbf\xbdファイル" > /tmp/test-garbled.txt
# フックスクリプトを手動実行
echo '{"tool_input":{"file_path":"/tmp/test-garbled.txt"}}' \
| ~/.claude/hooks/check-garbled-utf8.sh
# 期待される出力:
# {"decision":"block","reason":"UTF-8 broken chars detected in /tmp/test-garbled.txt. Fix the garbled text."}
ブロック応答が返ってくれば正常に動作しています。
余談: この記事を書いているときに Hook が発動した
実はこの記事自体が、フックの動作実績になりました。
この記事を Claude Code に書かせたところ、記事中の文字化けサンプルとして U+FFFD の実際の文字を本文に埋め込みました。すると、記事ファイルの書き込み直後に PostToolUse フックが発火し、自分自身が書いた記事をブロックしたのです。
PostToolUse:Write hook blocking error from command:
"~/.claude/hooks/check-garbled-utf8.sh":
UTF-8 broken chars detected in articles/claude-code-utf8-hooks.md.
Fix the garbled text.
フックからすれば「ファイルに U+FFFD が含まれている」という事実しか見えないので、それが意図的なサンプルであっても容赦なくブロックします。正しい挙動ですね。意図的に U+FFFD をファイルに書き込みたい場面はほぼないので、この検出で困ることは実質ありません。
今回は記事のサンプルとしてどうしても実文字が必要だったため、Write/Edit ツールを経由せず Bash から直接ファイルを編集することで Hook を回避しました。Hook は Claude Code のツールにだけ介入するため、シェルコマンドでの書き込みには発火しません。
おわりに
Claude Code の Hooks は、ツール実行のライフサイクルに自動化を差し込める強力な仕組みです。今回は UTF-8 文字化けの検出に使いましたが、フォーマッターの自動実行や、特定ファイルへの書き込み禁止など、さまざまな用途に応用できます。
日本語環境で Claude Code を使っているなら、この数行のスクリプトを入れておくだけで文字化けの見落としを防げます。ぜひ試してみてください。
Discussion