Claude Code でツール呼び出しが文字化けして実行されない ― 「一度出ると治らず悪化する」公式バグの正体と対処
まず結論:これは公式の既知バグ
Claude Code が使っていると頻繁に起きるアレへの対応方法。なんとなく上手く行っているので公開します。
Codeが呼び出しの XML タグ(<invoke> / antml:invoke)の生成に失敗し、court や call といった別トークンに化けて、ツールが実行されず、生のマークアップがチャットにそのまま表示される現象。
最悪なのは、一度起きると同じセッション内では回復せず、むしろ悪化していくこと。
しかもトークンは使う。サポート連絡しても音沙汰なし。
◯ソなんじゃないか?この会社。
ちなみに、このバグは、公式 Issue で世界から報告されている:
- #62407 Tool-call opening tag gets stuck on a malformed form and is unrecoverable within the session
- #64690 Opus 4.8 で antml:invoke が raw text になる
- #66153 tool-use markup が “court” になり実行されない
- #68354 内部 invoke XML がテキスト表示(Windows local + Cowork)
- #66400 “malformed and could not be parsed” でチャットにテキスト表示
原因(パーサではなくトークン生成の破損)
クライアントのパース不具合ではなく、モデルのトークン生成の問題。タグ名トークンが隣接トークンに化け(antml:invoke → antml:court 等)、ディスパッチャが解釈できず生テキスト化する。
化けた出力がコンテキストに残るため、モデルが自分の壊れた出力を真似て再生産する=セッション内で自己強化し回復しない。
出やすい条件:長いセッション/ツール呼び出しが多い/1メッセージに複数ツール/散文直後のツール呼び出し/背景 Bash の直後。
対処1:手動回復(一番確実)
セッション内では治らない。新規セッション or /clear で壊れたコンテキストを捨てるのが正攻法。粘ると悪化するだけ。
対処2:Node フックで自動検知&やり直し強制
「出たら自動で差し戻したい」場合は Stop フックを使う。ポイントは Windows では PowerShell でなく Node で書くこと。理由:
- フックは Git Bash 経由で実行され、
\を含む Windows パスが壊れる(.\x\y.ps1→.xy.ps1、#21878)。 - SessionStart で powershell.exe を spawn するとキーボードが固まる/ハングする(#26586)。
- → Node スクリプトを「絶対パス+フォワードスラッシュ」で呼ぶと両方回避できる。Node は Claude Code に必須なので必ず使える。
1) ガード本体 ~/.claude/check-invoke-leak.mjs
// 最終アシスタント出力に「ツール呼び出しタグの文字列」が漏れていたら、その回をやり直させる。
import { readFileSync } from "node:fs";
const get = (o, ks) => (o ? ks.map((k) => o[k]).find((v) => v !== undefined) : undefined);
let j = null;
try { j = JSON.parse(readFileSync(0, "utf8")); } catch {}
// ツール呼び出しの開きタグを検出する正規表現
const PAT = /<\s*(invoke\s+name=|antml:invoke\b)/i;
// その回の「最後のアシスタント発言」だけを見る(全履歴を見ると一度出た後ずっと再発火する)
let text = String(get(j, ["last_assistant_message"]) || "");
if (!text) {
const tp = get(j, ["transcript_path"]);
if (tp) {
try {
const lines = readFileSync(tp, "utf8").split("\n").filter(Boolean);
for (let i = lines.length - 1; i >= 0; i--) {
let e; try { e = JSON.parse(lines[i]); } catch { continue; }
const m = e.message;
if (m && m.role === "assistant" && Array.isArray(m.content)) {
text = m.content.filter((c) => c.type === "text").map((c) => c.text || "").join("\n");
break;
}
}
} catch {}
}
}
// stop_hook_active のとき(既にやり直し中)は再ブロックせず無限ループを防ぐ
if (PAT.test(text) && !get(j, ["stop_hook_active"])) {
process.stderr.write("Tool-call markup leaked as text (it did NOT execute). Redo this turn: emit a real tool call.\n");
process.exit(2); // Stop で exit 2 => Claude が継続(やり直し)
}
process.exit(0);
2) フック登録 ~/.claude/settings.json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "node C:/Users/<YOU>/.claude/check-invoke-leak.mjs"
}
]
}
]
}
}
Windows での必須ルール(ここを外すと動かない)
- 絶対パスにする(相対パス
./...は内部で.\...に正規化されて Git Bash に食われる)。 - フォワードスラッシュ(
C:/Users/...)。バックスラッシュは Git Bash が消す。 powershellでなくnode(SessionStart × powershell の固まり/ハングを回避)。- 反映はセッション開始時。設定後は新規セッションで確実に読み込む。
仕組み
Stop(応答完了)時にフックが走り、最終出力にタグ文字列が混入していたら exit 2。Claude Code はこれを「やり直せ」と解釈してターンを継続する。stop_hook_active で無限ループを防ぐ。あくまで対症療法で、根本(トークン生成)の修正は公式待ち。
まとめ
- 公式既知バグ:ツール呼び出しタグが化け → 生テキスト化 → セッション内で回復不能・悪化(#62407 ほか)。
- 手動なら新規セッション /
/clearが最速・確実。 - 自動化するなら Stop フックを Node で・絶対パス+フォワードスラッシュで(Windows のフック・パス/PowerShell バグを回避)。
Join the Discussion
コメントするにはログインしてください。
外部アカウントで本人性を担保し、スパムを抑えてコメントできます。