digiviceAI Native Blog

Article

Claude Code のInvoke問題

Signal OrbitObserve / Think / Build

Claude Code でツール呼び出しが文字化けして実行されない ― 「一度出ると治らず悪化する」公式バグの正体と対処

まず結論:これは公式の既知バグ

Claude Code が使っていると頻繁に起きるアレへの対応方法。なんとなく上手く行っているので公開します。

Codeが呼び出しの XML タグ(<invoke> / antml:invoke)の生成に失敗し、courtcall といった別トークンに化けて、ツールが実行されず、生のマークアップがチャットにそのまま表示される現象。

最悪なのは、一度起きると同じセッション内では回復せず、むしろ悪化していくこと。
しかもトークンは使う。サポート連絡しても音沙汰なし。

◯ソなんじゃないか?この会社。

ちなみに、このバグは、公式 Issue で世界から報告されている:

原因(パーサではなくトークン生成の破損)

クライアントのパース不具合ではなく、モデルのトークン生成の問題。タグ名トークンが隣接トークンに化け(antml:invokeantml: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 バグを回避)。

コメントを残す