長い Bash の出力や MCP サーバーからの応答を待っている最中に、突然画面に赤い文字で「Tool result could not be submitted」と出てきて固まる──これを Claude Code を日常的に使っている方なら一度は見たことがあるのではないでしょうか。私自身、累計5,000万DLのアプリ事業の保守を Claude Code で回すようになってから、特に MCP を増やしてきた時期に何度もこのエラーで詰まってきました。表示はそっけないのですが、ここで慌てて /cancel を連打したり、ターミナルごと閉じたりすると、せっかく数十分かけて積み上げた文脈ごと吹き飛ぶことがあります。
ここでは、私が現場で実際に組んでいる順序で、まず「何が起きているのか」を整理し、そのうえで切り抜け方とその後の予防策を共有していきます。
どんな瞬間に出てくるエラーか
Tool result could not be submitted は、Claude Code がツール呼び出し(Bash・Read・Edit・MCP ツール・カスタム MCP の戻り値など)の結果を Claude 本体に送り返す段階で失敗したときに表示されます。私の手元で頻度が高いのは、おおよそ次の4パターンです。
- MCP サーバーが想定外に大きな JSON(数百KB〜数MB)を一度に返した直後
Bashでfindやgrep -rを雑に投げて、出力が数万行に膨らんだとき- ストリーミング応答中に Wi-Fi が瞬断した、もしくは VPN が再接続した瞬間
- 連続して
/cancelを押した直後に、また新しい質問を入力したとき
いずれも共通しているのは「ツール側は仕事を終えているのに、結果を Claude に橋渡しできていない」という状態です。エラーメッセージは出力を返す側の責任に見えますが、本当の原因はその橋渡しの層にあることが多いです。
表面の原因と、本当の原因の区別
エラーを見たときに最初に疑いたくなるのは「ツールがクラッシュしたのでは」「MCP サーバーが落ちたのでは」という方向ですが、私は1997年に16歳でインターネットに触れて以来、独学でプログラミングを続けてきましたが、こういう「表面のメッセージ」と「本当の原因」がずれているエラーには何度も騙されてきました。
実体験から言うと、この症状で本当に怪しいのは次の3つです。
- ツール結果のペイロードサイズ: Claude Code は ToolResult のサイズに実装上の上限があり、超えると静かに弾かれます。
Bashの出力もここに含まれます。 - ToolUseId と ToolResultId のひも付け: 直前に
/cancelで別のツール呼び出しを中断した直後、新しい呼び出しの ID とずれることがあります。 - WebSocket/HTTPストリームの瞬断: 通信が一瞬切れただけでも、Claude 側は「結果を受け取れなかった」と判断します。
つまり、ツール自体は正常に終わっているのに、その結果を受け渡すレイヤーで失敗している、と捉えるのが現場の感覚に近いです。
まず試す即時対処(履歴をなるべく残す)
エラーを見た瞬間に大事なのは、慌ててセッションを壊さないことです。私の場合、次の順番でやっています。
- その場で Enter キーだけ押す(新しい入力は何も足さない)。ごく稀ですが、これでクライアント側が再送してくれることがあります。
- それでも復帰しなければ、
/compactを実行して、ここまでの会話を要約として圧縮します。これでコンテキストとセッション状態がいったん健全な形に整い直されます。 /compactが通ったら、自然な文章で「いまの作業を続けてください」と再開を促す。同じツールをもう一度呼ぶ場合は、引数を少し変える(例えばfindの-maxdepthを明示する)と再現を避けやすいです。- それでもダメな場合は、いったん 新しいセッションを開いて、
/compactで書き出した要約をコピペで渡す。完全に履歴を失うよりずっと傷が浅く済みます。
/cancel をいきなり連打するのはお勧めしません。中断状態がさらに不整合を呼んで、復旧がもっと難しくなることが多いという印象です。
再現パターン別の根本対処
ここからは、原因のあたりがついたあと、そもそも次から起こさないための具体的な手当てです。
MCP の戻り値が大きすぎる場合
これは私が一番ハマってきたパターンです。MCP サーバーで list_tickets や search_files のように、結果の件数が利用者次第になる API を作ると、すぐに数百KB に膨らみます。
// 改善前 — 全件返してしまう
server.tool("search_files", async ({ query }) => {
const all = await db.search(query);
return { content: all.map(toJSON) };
});
// 改善後 — 上限と続き取得を必ず持たせる
server.tool("search_files", async ({ query, cursor, limit = 20 }) => {
const page = await db.searchPaged(query, { cursor, limit });
return {
content: page.items.map(toJSON),
nextCursor: page.nextCursor,
};
});ポイントは2つで、1ツール呼び出しあたりの結果を必ず数十KB以内に抑えること、そして 続きが必要なときはカーソル方式で複数回に分けることです。Claude 側は呼び返しが得意なので、1回で全部返すより刻んだほうが結果として速く済みます。
Bash の出力が膨らんで弾かれる場合
Bash の戻りも同じ仕組みで弾かれます。コマンドを投げる前に、最終的にどれくらい出力されるかを意識する習慣をつけると、ぐっと安定します。
# 危険 — 何万行になるかわからない
grep -r "TODO" .
# 安全 — 件数を先に把握してから取りに行く
grep -rl "TODO" . | wc -l
grep -rl "TODO" . | head -50
# あるいは件数とサンプルだけ要約して返す
grep -r "TODO" . | head -200 > /tmp/todo.txt && wc -l /tmp/todo.txt私はプロジェクト直下に .claude/commands/safe-grep.md のような自作スラッシュコマンドを置いて、検索系コマンドは必ず head か wc で抑えるルールをコード化しています。
ToolUseId と ToolResultId のミスマッチ
/cancel を押した直後に別の指示を出すと、まだ送られていない ToolResult が宙ぶらりんになり、その後の呼び出しが弾かれることがあります。これに気づいてから、私は /cancel のあと最低1ターンは「いまの作業を一度終わりにして、状況を整理してください」と言葉で確認する ようにしました。一見遠回りに見えますが、結果的に復旧時間が一番短くなります。
ストリーミング断による失敗
VPN・モバイル回線・テザリングを切り替える瞬間にこれが起きやすいです。私の運用では、長めのタスク(マイグレーション、ビルド、大規模リファクタなど)に入る前に、
- 接続を有線または安定した Wi-Fi に固定する
- バックグラウンドの自動アップデートやクラウド同期を止めておく
Bashの長時間実行はnohupかtmux経由で投げて、後から結果ファイルを Read する
という地味な準備をしています。両祖父が宮大工で、出かける前に道具をきちんと整える姿を子どもの頃から見てきたのですが、開発も同じで、長丁場ほど「始める前」が品質を決めるなと感じています。
それでも詰まったときの安全弁
復旧不能な状態に陥った場合の、私の最終フォールバックを共有します。
- 別タブで
claude /resumeを試して、直前のセッションを引き取れるか確認する - ダメなら、新セッションで
cat .claude/last_session_summary.mdのように、事前に書き出した要約から再開する - プロジェクト直下に
CLAUDE.mdで「セッションが詰まったら、まずgit statusとgit log -3を確認し、現在地を報告すること」と書いておく
3つ目は地味ですが、Claude が再開時に必ず現状確認から入ってくれるので、私が口頭で説明し直す手間が劇的に減りました。離れて暮らす自分の子どもたちに何かを引き継ぐ感覚に近くて、未来の自分や同僚に向けた「申し送り書」をプロジェクトに残しておく、と思うと書きやすくなります。
次に試してほしい1つのこと
このエラーに何度か出会ったことがあるなら、今日のうちにプロジェクトの CLAUDE.md に次の一行を書き足してみてください。
- Bash の grep/find は必ず `wc -l` で件数を確認してから取得する
- MCP ツールの結果は1回あたり20件以下のページングで返すたったこれだけですが、Tool result could not be submitted の発生頻度が体感で半分以下になります。お読みいただきありがとうございました。同じところで詰まっている方の助けになれば嬉しいです。