Claude Code の指定は API リクエストのどこに載るか

目次
  1. 「効いているはず」を確かめる方法がない
  2. claude -p 1回で4リクエスト
  3. ファイル系の入力は「会話」に入る
  4. CLAUDE.md は system 配列ではない
  5. サブディレクトリの CLAUDE.md は遅れて届く
  6. @ファイル は「読んだこと」になる
  7. フラグは「聞くもの」と「聞かないもの」に分かれる
  8. ローカルで裁かれるもの、全量再送されるもの
  9. 書いたものはどこへ行くのか

CLAUDE.md に書いた指示や --model の指定は、本当にモデルに届いているのか。Claude Code の出力からは分からない。「効いているはず」を前提に使い続けると、届いていない指定を当てにして判断を誤る。そこで claude -p が Anthropic API に投げるリクエストを、localhost に立てたモックで受け止めて中身を読んだ。

「効いているはず」を確かめる方法がない

Claude Code の「使い方」は、モデルへの入力を増やす手段が多い。CLAUDE.md、@ でのファイル指定、起動フラグ、スラッシュコマンド、エージェント定義。どれも「指定が API リクエストにどう載るか」を見せてはくれず、返ってきたテキストから推測するしかない。

通信の中身を見る記事は既出もある。私がやったのはより小さい版で、リクエスト先を localhost のモックに向けるだけだ。Claude Code は ANTHROPIC_BASE_URL でエンドポイントを変えられるので、そこでリクエスト本文を JSONL に記録する HTTP サーバー(Python 標準ライブラリのみ・約60行)を立てた。ANTHROPIC_API_KEY にはダミーを入れ、レスポンスは決め打ちの end_turn と、必要なときだけ tool_use を返す。

環境は Claude Code 2.1.267、macOS 26.6.1、Python 3.14.7。

$ sh -c 'claude --version; sw_vers; python3 -V'
2.1.267 (Claude Code)
ProductName:		macOS
ProductVersion:		26.6.1
BuildVersion:		25G76
Python 3.14.7
...
# exit=0 (30 ms)

モックが受けるのは /v1/messages への POST だけ。これで「何を指定したら、リクエストのどこが変わるか」を1項目ずつ採取できる。

claude -p 1回で4リクエスト

まず何も置かない空のディレクトリで claude -p "Reply with the word PONG only" を1回打つと、モックには4件届いた。

$ sh -c 'source scripts/lib.sh && ensure_server && cap d_base "PONG" "Reply with the word PONG only"'
...
requests captured: 4
=== req[0] /v1/messages?beta=true
  model=claude-opus-5  body_bytes=4120
  top-level keys: ['max_tokens', 'messages', 'metadata', 'model', 'output_config', 'stream', 'system', 'thinking', 'tools']
  system: 3 block(s)
    system[0] 74 chars | canary=[] | head: x-anthropic-billing-header: cc_version=2.1.267.8fc; cc_entrypoint=sdk-cli;
    system[1] 62 chars | canary=[] | head: You are a Claude agent, built on Anthropic's Claude Agent SDK.
    system[2] 3059 chars | canary=[] | head: You are naming a coding session so the user can pick it out of a long list of se
    msg[0] role=user block0<text> 230 chars | canary=[('PONG', 30)] | head: <session> Reply with the word PONG only </session>  Write the title in the predominant lan
  tools: 0 -> []
...
# exit=0 (7142 ms)

先の2件(約4KB・tools 0・出力に {title} の JSON スキーマ指定)はセッション一覧用のタイトル生成で、system[2] の冒頭が「You are naming a coding session…」になっている。後の2件が本呼び出しだ。それぞれ stream:true と stream:false のペアで計4件だった。

本呼び出しは約77KB。system は3ブロックで、先頭74文字は x-anthropic-billing-header の文字列(バージョンと cc_entrypoint=sdk-cli を含む)が置かれていた。tools は22個。user メッセージは <system-reminder> のコンテキストブロック+プロンプト本文の2ブロック。さらに messages の中に role: "system" のメッセージが1本あり、そこに環境依存のエージェント一覧が載っていた。

$ sh -c 'source scripts/lib.sh && ensure_server && cap d_base "PONG" "Reply with the word PONG only"'
...
=== req[2] /v1/messages?beta=true
  model=claude-opus-5  body_bytes=77180
...
  tools: 22 -> ['Agent', 'Bash', 'CronCreate', 'CronDelete', 'CronList', 'DesignSync', 'Edit', 'EnterWorktree', 'ExitWorktree', 'ListAgents', 'NotebookEdit', 'Read', 'ReportFindings', 'ScheduleWakeup', 'SendMessage', 'Skill', 'TaskOutput', 'TaskStop', 'WebFetch', 'WebSearch', 'Workflow', 'Write']
...
# exit=0 (7142 ms)

ファイル系の入力は「会話」に入る

CLAUDE.md は system 配列ではない

ディレクトリに KIBANANA-77 という合い言葉だけを書いた CLAUDE.md を置いて同じプロンプトを打つと、canary は system 配列のどこにも出ず、最初の user メッセージの先頭ブロックに出た。

$ sh -c 'source scripts/lib.sh && ensure_server && cap d_claudemd "KIBANANA-77" "Reply with the word PONG only"'
...
    msg[0] role=user block0<text> 762 chars | canary=[('KIBANANA-77', 532)] | head: <system-reminder> As you answer the user's questions, you can use the following context: #
...
# exit=0 (6857 ms)

このブロックは306→762 chars に膨らんでおり、中身は <system-reminder> で、# claudeMd 節に「Contents of <パス> (project instructions, checked into the codebase)」として CLAUDE.md の本文が入っていた。@./extra.md でファイルをインポートした場合も、インポート先の IMPORT-OK-31 が同じブロックに展開されていた(007 ログ。block0 は996 chars)。

「CLAUDE.md はシステムプロンプトとして強い位置に置かれる」という説明を時々見るが、少なくとも -p のワイヤ上では user メッセージ内のコンテキスト注記だった。

サブディレクトリの CLAUDE.md は遅れて届く

ルートと sub/ に別の canary を置き、モックには「TOOLCALL-READ sub/note.txt」というプロンプトに対して Read の tool_use を返させた(エージェントにファイルを読ませる演出)。num_turns: 2 で2往復した。

$ sh -c 'source scripts/lib.sh && ensure_server && cap d_sub "ROOT-CL-11,SUBDIR-99" "TOOLCALL-READ sub/note.txt"'
...
    msg[0] role=user block0<text> 732 chars | canary=[('ROOT-CL-11', 503)] | head: <system-reminder> As you answer the user's questions, you can use the following context: #
...
    msg[2] role=assistant block0<tool_use> 29 chars | canary=[] | head: {"file_path": "sub/note.txt"}
    msg[3] role=user block0<tool_result> 37 chars | canary=[] | head: "1\tA note in the subdirectory.\n2\t"
...
# exit=0 (6868 ms)

初回リクエストには ROOT-CL-11 だけがあり SUBDIR-99 は無い。sub/note.txt を Read した tool_result が返った後の2回目では、messages の末尾に role: system の新しいメッセージ(msg[4]・226 chars)が追加され、その中に SUBDIR-99 があった(008 ログでは canary=[('SUBDIR-99', 164)])。

つまり下の階層の CLAUDE.md は起動時には読まれず、ファイルに触れたタイミングで別メッセージとして会話に積まれる。しかも置き場所はルートの CLAUDE.md(user メッセージ内)と違う。

@ファイル は「読んだこと」になる

プロンプトに @a.txt と書いた場合、ファイル本文は user メッセージには来ない。代わりに role: system のメッセージが立ち、「Called the Read tool with the following input: …」「Result of calling the Read tool: …」という、Read ツールを呼んだ形の記録としてファイル内容(ATFILE-55)が入っていた(009 ログ。msg[1]・10013 chars、canary offset 238)。

$ sh -c 'source scripts/lib.sh && ensure_server && cap d_at "ATFILE-55" "What marker is in @a.txt ?"'
...
    msg[0] role=user block0<text> 306 chars | canary=[] | head: <system-reminder> As you answer the user's questions, you can use the following context: #
    msg[0] role=user block1<text> 26 chars | canary=[] | head: What marker is in @a.txt ?
...
# exit=0 (6853 ms)

フラグは「聞くもの」と「聞かないもの」に分かれる

同じ要領で起動フラグを1個ずつ変えて差分を取った。

指定 リクエストの変化
--model haiku model が claude-haiku-4-5-20251001 に。tools は22→26(TaskCreate 系4個が増)、system[2] が9772→27954 chars、thinking が enabled・budget 31999 に
--append-system-prompt system[2] の末尾に追記される。system[1] の自己紹介行も別文言に変わる
--disallowedTools Bash Bash が tools から消える。ただし個数は22→23で Glob と Grep が新たに出現した
--disallowedTools Write Edit 22→20。指定分が消えるだけ
--allowedTools Read tools 配列は不変(22個のまま)
--add-dir system[2] の「Additional working directories」に追加される
--agents エージェント一覧に名前と説明だけ載る。prompt フィールドの文字列は全フィールド走査でも出ない
--bare system[2] が9772→408 chars、tools が22→3。CLAUDE.md もエージェント一覧も無し
--effort low output_config.effort が high→low になるだけ

--model は「モデル名だけ変わる」変更ではなかった。別モデル用のシステムプロンプト・別ツールセット・別 thinking 設定が丸ごと差し替わっていた。また --disallowedTools が tools 配列そのものを変えるのに対し、--allowedTools は配列を変えない。「許可」はワイヤではなくクライアント側の実行許可層として効いていることになる。

$ sh -c 'bash scripts/exp_flags2.sh'
...
  tools: 23 -> ['Agent', 'CronCreate', 'CronDelete', 'CronList', 'DesignSync', 'Edit', 'EnterWorktree', 'ExitWorktree', 'Glob', 'Grep', 'ListAgents', 'NotebookEdit', 'Read', 'ReportFindings', 'ScheduleWakeup', 'SendMessage', 'Skill', 'TaskOutput', 'TaskStop', 'WebFetch', 'WebSearch', 'Workflow', 'Write']
...
########## allowed-read :: claude -p Reply with PONG --allowedTools Read
...
  tools: 22 -> ['Agent', 'Bash', 'CronCreate', 'CronDelete', 'CronList', 'DesignSync', 'Edit', 'EnterWorktree', 'ExitWorktree', 'ListAgents', 'NotebookEdit', 'Read', 'ReportFindings', 'ScheduleWakeup', 'SendMessage', 'Skill', 'TaskOutput', 'TaskStop', 'WebFetch', 'WebSearch', 'Workflow', 'Write']
...
# exit=0 (34469 ms)

--add-dir が system[2] の作業ディレクトリ一覧に現れた様子はこれ(パスは <ws> に置き換えてある)。

$ sh -c 'bash scripts/exp_extra2.sh'
...
system[2]:  - Primary working directory: <ws>/d_base
system[2]:  - Is a git repository: true
system[2]:  - Additional working directories:
...
system[2]:   - <ws>/d_claudemd
system[2]:  - Platform: darwin
...
# exit=0 (7109 ms)
77166bytes無指定75498bytes--disallowedTools Write Edit4866bytes--bare123134bytes--model haiku
本呼び出しリクエストの本体サイズ

つまずきも1つある。最初、フラグをプロンプトの前に置いて claude -p --disallowedTools Bash "Reply with PONG" と打ったら、可変長引数がプロンプト文字列までツール名として食い、プロンプト未指定エラーで exit=1。リクエストは0件だった。

$ sh -c 'bash scripts/exp_flags.sh'
...
########## disallowed-bash :: claude -p --disallowedTools Bash Reply with PONG
[lib] claude exit=1
...
      Error: Input must be provided either through stdin or as a prompt argument when using --print
...
requests captured: 0
...
# exit=0 (61916 ms)

「載らなかった」のではなく「そもそも実行されていない」。「効いていない」を測るつもりの実験で、自分が同じ罠を踏んだ。

ローカルで裁かれるもの、全量再送されるもの

存在しないスラッシュコマンド claude -p "/nosuchcmd" は、stdout に Unknown command: /nosuchcmd を出して終わる。このときモックに届くのはタイトル生成用の2件だけで、tools 付きの本呼び出しは走らない。コマンド名の解決はローカル側で行われている。

$ sh -c 'bash scripts/exp_extra.sh'
...
########## unknown-cmd request type
requests captured: 2
...
# exit=0 (16123 ms)

続く2行は、どちらも tools=0・出力スキーマ指定あり・system[2] の冒頭が「You are naming a coding session」だった(016 ログ)。会話の継続は claude -p --resume <session_id> で試した。同じ session_id が維持され、2ターン目のリクエストには1ターン目のプロンプト・assistant の応答・system メッセージがそのまま全部乗っていた。

$ sh -c 'bash scripts/exp_resume3.sh'
...
### turn2: claude -p --resume $SID
t2 result: MOCK-REPLY-42
t2 session_id: cd7ae866-8d67-4fe0-b8f0-f6106853ff7d
...
    msg[2] role=assistant block0<text> 13 chars | canary=[('MOCK-REPLY-42', 0)] | head: MOCK-REPLY-42
    msg[3] role=user block0<str> 21 chars | canary=[('MARKER-TURN2', 4)] | head: Say MARKER-TURN2 once
    msg[4] role=system block0<text> 49 chars | canary=[] | head: <total_tokens>15000000 tokens left</total_tokens>
...
# exit=0 (866 ms)

Messages API はステートレスなので履歴の再送自体は当然だが、9760 chars のエージェント一覧も毎回載るのはコスト感覚としては意外だった。

なお、この測定ではセッションの保存先を CLAUDE_CONFIG_DIR で隔離ディレクトリに向けて行った。隔離しない今回の環境では --resume が「No conversation found with session ID」を返して継続できなかった(014 ログ)。継続できるかは環境に依存し得るので、この挙動は一般化しない。

書いたものはどこへ行くのか

測った限りの対応表はこうなった。

入力 届く場所
CLAUDE.md(カレント) 最初の user メッセージ内 <system-reminder> の # claudeMd 節
CLAUDE.md の @import 同上(展開済みの本文として)
サブディレクトリの CLAUDE.md その階層のファイルを読んだ後、追加の role: system メッセージ
プロンプト内 @ファイル role: system メッセージ内の「Read を呼んだ結果」として
--model model フィールド+システムプロンプト・ツールセット・thinking 一式
--disallowedTools tools 配列から除去(構成が組み替わる場合あり)
--allowedTools tools 配列は不変(実行許可は別層)
--append-system-prompt system[2] の末尾
--add-dir system[2] の working directory 一覧
--agents エージェント一覧に名前列挙。prompt 本文は初期リクエストには無し
--bare ほぼ全部無し(system 408 chars・tools 3)
存在しない /コマンド どこにも。本呼び出し自体が走らない
--resume 新規入力ではなく履歴全量の再送

読み取れることは2つある。1つ目は、system 配列に入ると思われがちな指定(CLAUDE.md・@ファイル)が、実際には user メッセージか会話内の role: system メッセージとして運ばれていること。2つ目は、--allowedTools のように「効いているがリクエストには現れない」層があること。ワイヤに見えなくても無効とは限らない。

限界もある。この方法で確かめられるのは「何が・どこに送られたか」までで、system メッセージと user メッセージでモデル内の効きがどう違うか、CLAUDE.md とプロンプトが矛盾したときどちらが効くかは分からない(返答はモックの決め打ちなので、モデルの解釈は観測対象外)。また ~/.claude/ 配下のユーザー級メモリ・カスタムコマンド・agents はこの環境では作れず、未検証に留めた。

不審な指定が出たときは、同じ手順で自分の環境を覗けばよい。モックは /v1/messages を受けて中身を JSONL に書くだけの数十行で、外部アカウントも課金も要らない。「書いたつもり」は、届いたかどうかを見てから「書いた」に変えられる。

関連書籍