SapeetでSWEをやっている林です。
毎日 Claude Code を使っていると、「このリポジトリの差分を確認してほしい」と一言打つだけで、いつの間にかコミット内容がまとめられて返ってくる。便利すぎて当たり前になっているのですが、ふと「裏側で何が起きているんだろう?」と気になりました。
調べてみると、1回の発言に対して内部では 6回以上の API 呼び出し が走っていて、Claude 自身はツールを一切実行していないことがわかりました。この記事では、その仕組みを順を追って解説します。
この記事でわかること
- なぜ Claude 自身はコマンドを実行できないのか
- ツールの呼び出しがどういう仕組みで動いているか
- なぜ1回の質問で複数のターンが必要になるのか
- Claude がいつ「もう終わり」と判断しているのか
- プロンプトキャッシュがなぜ効いてくるのか
まず claude-tap を入れると「見える」ようになる
説明に入る前、まず内部の動きを可視化するツールを紹介します。
claude-tap は、Claude Code と Anthropic API の間に入るローカルプロキシです。Claude Code が送っているリクエストとレスポンスをすべて傍受して、ブラウザのビューアーで確認できるようにしてくれます。
Claude Code → claude-tap(ローカルプロキシ) → Anthropic API
↓
.traces/ にログ保存
ブラウザで確認可能
system prompt の全文・ツール定義・各ターンの messages・トークン数・キャッシュの状況など、通常は見えないものがすべて確認できます。
インストール
Python 3.11 以上が必要です。
# uv を使う場合(推奨)
uv tool install claude-tap
# pip を使う場合
pip install claude-tap
使い方
# Claude Code を claude-tap 経由で起動するだけ
claude-tap
# リアルタイムでブラウザに表示しながら起動
claude-tap --tap-live
claude-tap が Claude Code を自動で起動してくれます。セッション終了後、.traces/ ディレクトリにログが保存されます。
.traces/
└── 2026-05-10/
├── trace_134932.jsonl # 完全なリクエスト/レスポンス(JSON Lines)
└── trace_134932.log # 人間が読みやすいサマリー
大前提:Claude はコマンドを「実行」できない
まず、大事な前提から。
Claude はテキストを受け取ってテキストを返す推論エンジンです。ファイルの読み書きも、シェルコマンドの実行も、Claude 自身には一切できません。
では「Bash を実行して」という要求をどう処理しているのか。
答えは、「実行してほしいツールの名前と引数を JSON で出力するだけ」 です。実際に実行するのは Claude ではなく、ローカルで動いている Claude Code(クライアント) です。
ポイント: Claude は「何をすべきか」を判断して JSON を出力する係。Claude Code は「その JSON を受け取って実際に動かす」係。この分業がすべての出発点です。
ツール呼び出しの仕組み
リクエストの中に「使えるツールの一覧」が入っている
Claude Code が API にリクエストを送るとき、tools という形でツールの定義を一緒に渡しています。今回のセッションでは 30 個のツールが定義されていました(Bash、Read、Write、Edit など)。
⚠️ ここが重要: Claude は tool_use ブロックを出力した時点で応答を止めます。クライアントがツールを実行して結果を返すまで、Claude は次の推論を行いません。これが Turn が複数必要になる根本的な理由です。
実際のログを見てみると
Turn 3 の Claude レスポンス(.jsonl から抜粋)
{
"content": [
{
"type": "text",
"text": "リポジトリの差分を確認します。"
},
{
"type": "tool_use",
"id": "toolu_01TY2z...",
"name": "Bash",
"input": {
"command": "git status && git diff HEAD"
}
}
],
"stop_reason": "tool_use"
}
Turn 4 でクライアントが送る tool_result
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01TY2z...",
"content": "On branch main\nYour branch is ahead of 'origin/main' by 1 commit..."
}
]
}
tool_use → ツール実行 → tool_result → 次の推論、この1サイクルが「1ターン」に相当します。
Claude はツール説明から「どのツールを使うか」を判断している
疑問に思った方もいると思います。Claude はなぜ「git status を使おう」と判断できるのか。
答えはシンプルで、毎回のリクエストにツールの説明文(description)が含まれているから です。Claude はその説明をテキストとして読んで判断しているだけです。特別な訓練や記憶があるわけではありません。
{
"name": "Bash",
"description": "Executes a given bash command and returns its output...(詳細な説明)",
"input_schema": {
"type": "object",
"properties": {
"command": {"type": "string"},
"timeout": {"type": "number"}
},
"required": ["command"]
}
}
💡 ポイント: description の質がそのままツール選択の精度に直結します。カスタムツールを作るときに説明文が重要な理由はここにあります。
なぜ Turn 4・5・6 がそれぞれ必要なのか
「差分を確認して」という一言に対して、なぜ 4 ターンも必要なのか。
理由は「前のツールの結果を見てから次のツールを選ぶ」ためです。Claude は1回の応答で実行結果を受け取ることができないので、確認 → 判断 → 確認 → 判断 のサイクルが必然的に発生します。
Turn 3: Claude が初回推論
→ tool_use: Bash("git status && git diff HEAD")
↓ Claude Code が実行・結果を取得
Turn 4: Claude が「origin/main より 1 コミット進んでいる」を読んで判断
→ tool_use: Bash("git log origin/main..HEAD --stat")
↓ Claude Code が実行・結果を取得
Turn 5: Claude がコミットハッシュを確認して判断
→ tool_use: Bash("git show HEAD")
↓ Claude Code が実行・結果を取得
Turn 6: Claude がコミット全内容を受け取って最終回答を生成
→ text: "現在の差分の概要です..."
stop_reason: "end_turn" → ループ終了
Turn が進むごとに messages が積み上がっていきます。
| Turn | messages 数 | 追加されたもの |
|---|---|---|
| 3 | 1 | ユーザーの発言のみ |
| 4 | 3 | + Turn3 の assistant 応答 + git status 結果 |
| 5 | 5 | + Turn4 の assistant 応答 + git log 結果 |
| 6 | 7 | + Turn5 の assistant 応答 + git show 結果 |
ちなみに: Claude は1回の応答に複数の tool_use を含めることもできます(並列呼び出し)。ただし今回は「git log の結果に含まれるコミットハッシュを確認してから git show を呼ぶ」という順序の依存があったため、ターンが分かれています。
ループを終了させるのも Claude
「いつ終わるか」を決めているのは Claude Code ではなく、Claude 自身 です。
Claude のレスポンスには必ず stop_reason が含まれていて、Claude Code はその値を見てループを続けるか終わるかを判断しています。
| stop_reason | 意味 | Claude Code の動作 |
|---|---|---|
"tool_use" |
まだツールを呼びたい | ツールを実行して結果を返す → 次ターンへ |
"end_turn" |
回答が完成した | ユーザーに表示してループ終了 |
# エージェントループの本質(擬似コード)
while True:
response = client.messages.create(...)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason == "end_turn":
break # ← Claude が「終わり」と判断した
tool_results = [execute(block) for block in response.content if block.type == "tool_use"]
messages.append({"role": "user", "content": tool_results})
今回のセッションでは git show HEAD の結果でコミットの全内容が把握できたため、Claude が Turn 6 で end_turn を返してループが終わっています。
補足:client-side tools と server-side tools の違い
ここまで「Claude Code がローカルでツールを実行する」という話をしてきましたが、実はツールにはもう一種類あります。
今回使ってきた Bash や Read のように クライアントが実行するもの(client-side tools) と、Anthropic のサーバー側で処理される server-side tools です。Claude Code を使う分には意識する機会は少ないですが、API を直接触るようになると知っておくと便利です。
client-side tools(今回使ったもの)
[Claude] → tool_use を出力
[Claude Code] → ローカルで実行
[Claude Code] → tool_result を API に送信
[Claude] → 結果を受け取って次の推論
Claude Code の 30 ツール(Bash, Read, Write, Edit, WebFetch...)はすべてこちらです。
server-side tools(Anthropic が提供)
[Claude] → ツールを内部的に呼び出し
[Anthropic サーバー] → 実行して Claude に結果を返す
[Claude] → 結果を受け取って次の推論
クライアントは tool_result を用意する必要がなく、Claude が自律的に結果を得られます。
代表的な server-side tools
| ツール | 概要 |
|---|---|
web_search |
Web 検索を実行して結果を返す |
| Computer Use | スクリーンショット・クリック・キーボード操作 |
| Code execution | Python をサンドボックスで実行(claude.ai Artifacts) |
どちらを使うか
| 操作の種類 | 推奨 |
|---|---|
| ローカルファイルの読み書き | client-side(Read, Write, Edit) |
| シェルコマンドの実行 | client-side(Bash) |
| Web の情報取得 | server-side(web_search)または client-side(WebFetch) |
| スクリーン操作 | server-side(Computer Use) |
Claude Code のアーキテクチャ全体像
ここまでの内容をまとめると、Claude Code は次のような構造で動いています。

Claude(クラウド)が担うこと
- ツールの
descriptionを読んで「何をどう呼ぶか」を判断する -
tool_resultを読んで次のアクションを決める -
stop_reason: "end_turn"でループ終了を宣言する
Claude Code(ローカル)が担うこと
-
stop_reasonを見てループを回し続ける - ツールを実際に実行する
- 会話履歴とキャッシュを管理する
- Haiku で軽量な事前処理(クォータ確認・タスク分類)を行う
💡 一言で言うと: Claude は「考える人」、Claude Code は「手と足を持つ実行者」です。
プロンプトキャッシュがターンをまたいで効いてくる
claude-tap のトークン数を見ると、Turn 3 以降で cache_read が増え続けているのが確認できます。
| Turn | cache_create | cache_read | 効果 |
|---|---|---|---|
| 3 | 27,529 | 0 | 初回:system prompt をキャッシュ |
| 4 | 229 | 27,529 | Turn3 のキャッシュを再利用 |
| 5 | 206 | 27,758 | Turn3+4 のキャッシュを再利用 |
| 6 | 424 | 27,964 | Turn3+4+5 のキャッシュを再利用 |
Turn 3 で CLAUDE.md を含む system prompt(27,529 トークン)を一度キャッシュしてしまえば、Turn 4〜6 はそれを cache_read で再利用できます。cache_read の料金は通常の 90% オフ なので、ターンが増えるほどコスト削減効果が大きくなります。
おわりに:すっきりした
claude-tap でログを眺めていたら、「なんとなく動いている」だったものが一気に解像度が上がりました。
一言打つたびに、Haiku がクォータを確認して、タスクを分類して、Sonnet が description を読んでツールを選んで、結果を解釈して、「もういい」と判断するまでループを回し続けている——そういう仕組みが裏で動いていたんですね。
特に「Claude はコマンドを実行していない」という事実は、使い始めた頃に知りたかったと思いました。Claude はあくまで「次に何をするかを考える係」で、実際に手を動かしているのは Claude Code 側。この分業を理解してからは、プロンプトの書き方やカスタムツールの設計に対する考え方も少し変わりました。
毎日お世話になっているツールの裏側を知ると、なんとなく愛着が増す気がします。みなさんもぜひ一度 claude-tap で覗いてみてください。
一緒に「裏側」を作りませんか
ここまで読んでいただきありがとうございました。
「自分もこういう仕組みを掘り下げたい」と思った方、Sapeet ではまさにそういったエンジニアを探しています。弊社について少しでも気になっていただけた方は、ぜひこちらも覗いてみてください。




