Claude Code のサブエージェントは、.claude/agents/ に Markdown を1枚置くだけで作れます。ただ、定義の本文に「読み取り専用です。ファイルを変更してはいけません」と書いても、それは権限の設定ではありません。 使えるツールを決めるのは、先頭の frontmatter の tools: です。
自分はサブエージェントを10体以上、定義ファイルで運用しています。その中に、定義の説明文に「読み取り専用」と書いてあるのに、Bash を持っているので echo > file や git commit ができる状態の定義があります。分析のスクリプトを動かすために Bash が要るからで、今も残しています。本文の「読み取り専用」は「お願い」でしかなく、ツールで強制できていません。
この記事は、最初から1体作って、次の4点を実機で確かめる手順です。
- 定義を置いて、呼び出せることを確認する
-
tools:を書かないと、書き込みも Bash も使えてしまうのを見る -
tools: Read, Grepに絞ると、Write も Bash も渡されないのを見る - 詰まりどころ(
disallowedToolsの落とし穴、ツール名のタイプミス)
動作確認した環境
- Claude Code 2.1.317(2026-11-07 に実機確認)
- macOS / zsh(Windows・Linux では確かめていません)
- 検証は使い捨ての git リポジトリで、
claude -p(ヘッドレスモード)から行いました。以降のパスは<テスト用ディレクトリ>と書きます
手順1: 定義ファイルを置く
プロジェクト専用のサブエージェントは .claude/agents/ に置きます。公式ドキュメントによると、置き場所は優先度の高い順に次のとおりです。
| 優先度 | 置き場所 | 範囲 |
|---|---|---|
| 1 | 組織の管理設定 | 組織全体 |
| 2 |
--agents CLI フラグ |
そのセッションだけ |
| 3 | .claude/agents/ |
そのプロジェクト |
| 4 | ~/.claude/agents/ |
自分の全プロジェクト |
| 5 | プラグインの agents/
|
プラグインを有効にした範囲 |
同じ name が複数あれば、優先度の高いほうが使われます。必須の frontmatter は name と description の2つだけです。description は、Claude が「いつこのエージェントに任せるか」を決める文なので、用途を具体的に書きます。
テスト用のリポジトリを作って、最初の定義を置きます。ここでは、あえて本文に「読み取り専用」と書き、tools: は書かない版にします。
mkdir -p <テスト用ディレクトリ>/.claude/agents && cd <テスト用ディレクトリ>
git init -q && echo "hello" > sample.txt
.claude/agents/reviewer.md
---
name: reviewer
description: コードを読んで指摘だけする担当。sample.txt などの確認を頼まれたときに使う。
---
あなたはレビュー担当です。**読み取り専用**です。ファイルを変更してはいけません。指摘を返すだけにしてください。
公式ドキュメントによると、~/.claude/agents/ と .claude/agents/ は Claude Code が監視していて、ファイルを足したり編集したりすれば数秒で反映され、再起動は要りません。例外は、その範囲で最初のエージェントファイルを新しい agents ディレクトリに作ったときなどで、再起動が必要です。今回は定義を置いてから claude -p を起動しているので、この問題には当たりませんでした。
手順2: 呼び出せることを確認する
名前を指定して頼めば、Claude が委譲します。公式ドキュメントでは、ほかに @agent-<名前> で明示的に指定する方法と、claude --agent <名前> でセッション全体をそのエージェントとして動かす方法も紹介されています(この2つは今回は試していません)。
claude -p "reviewer エージェントに委譲して、sample.txt の中身を読んで1行で報告させて。あなた自身は読まず、必ず reviewer に任せて。最後に、使ったエージェント名を書いて" \
--allowedTools "Agent,Read" < /dev/null
返ってきたのはこうでした。
reviewer の報告では、`sample.txt` の中身は「hello」の1行だけです(ファイルは変更していません)。
使ったエージェント名: **reviewer**
--output-format stream-json --verbose を付けると、メインが呼んだツールの記録が JSON で出ます。jq で見ると、Agent ツールが subagent_type: "reviewer" で呼ばれていました。これで「呼び出せた」と言えます。
手順3: tools を書かないと、何が使えるか
まず、本文に「読み取り専用」と書いた reviewer に、わざと書き込みを頼みました。
claude -p "reviewer エージェントに委譲して、sample.txt を読み、指摘を report.txt に保存させて(保存は Write ツールか Bash のどちらでもよい)。reviewer が実際に呼んだツール名を報告させて。" \
--allowedTools "Agent,Write,Bash,Read" --output-format stream-json --verbose < /dev/null
結果は、report.txt は作られませんでした。 reviewer は Read を1回呼んだだけで、保存は「読み取り専用と書かれているから」と自分で断っています。Write や Bash の許可が無かったからではありません。許可は渡していました。同じ条件で Write と Bash に直接書き込みを頼む版も試しましたが、同じく断られました(2回とも)。
ここで「本文に書けば守られる」と思ってはいけません。断ったのはモデルが文章を守ったからで、仕組みで止めたわけではないからです。確かめるために、本文に書き込みを禁じる文が無い版を作ります。tools: は書きません。
.claude/agents/notes-open.md
---
name: notes-open
description: ファイルを読んで要点をまとめる担当。tools を指定していない版。
---
あなたは要点まとめ担当です。頼まれたら、ファイルを読んで結果を返します。
claude -p "notes-open エージェントに委譲して、自分が使えるツール名を全部列挙させて。その後、Write ツールで written-open.txt に「open」と書かせて。あなた自身は書かない。結果をそのまま報告して。" \
--allowedTools "Agent,Write,Bash,Read" < /dev/null
結果は次のとおりです。
- ツールの列挙:
BashEditReadWriteSkillToolSearchなど、メインに近い一式に加えて、自分の環境に接続している MCP サーバーのツール(外部サービスの API を叩くもの)まで入っていました(MCP を接続していない環境なら、この部分は出ません) -
written-open.txtが作られ、中身はopenでした(メインは書いていません。エージェントがWriteで書いています)
公式ドキュメントの記述どおりです。
Inherits every tool available to subagents if omitted.(
toolsを省略すると、サブエージェントが使えるツールをすべて継承する)
つまり、tools: を書かない定義は「全部使える」定義です。本文に何と書いてあっても、この状態は変わりません。
手順4: tools: Read, Grep に絞る
同じ定義に tools: を1行足します。
.claude/agents/notes-ro.md
---
name: notes-ro
description: ファイルを読んで要点をまとめる担当。tools を Read, Grep に絞った版。
tools: Read, Grep
---
あなたは要点まとめ担当です。頼まれたら、ファイルを読んで結果を返します。
claude -p "notes-ro エージェントに委譲して、(a) 自分が使えるツール名を、MCP 以外すべて列挙させて(MCP は個数だけ)。(b) Write ツールで written-ro.txt に「ro」と書こうとさせる (c) Bash ツールで echo b > written-ro-bash.txt を実行しようとさせる。各結果を全文そのまま報告させて。" \
--allowedTools "Agent,Write,Bash,Read" < /dev/null
結果です。
- 使えるツール:
ReadとGrepの2個。MCP ツールは0個 - Write も Bash も、ツール一覧に無い(呼び出せない)
-
written-ro.txtもwritten-ro-bash.txtも作られていない
ここで注意してほしいのは、「実行したら拒否された」ではないことです。エラーメッセージはありません。そのツールが最初から渡されていないので、エージェントは呼ぶことができません。権限のダイアログで止めるのではなく、手札から外す仕組みです。メイン側に --allowedTools "Write,Bash" を渡していても、結果は変わりませんでした。
これが「tools: は許可リスト」という意味です。公式ドキュメントでも、ツールの制限には tools(許可リスト)か disallowedTools(拒否リスト)を使うと書かれています。
詰まりどころ
disallowedTools: Write, Edit だけでは、Bash から書ける
「書き込みを止めたい」だけなら、disallowedTools: Write, Edit で足りそうに見えます。試しました。
---
name: notes-deny
description: ファイルを読んで要点をまとめる担当。disallowedTools で Write, Edit だけ外した版。
disallowedTools: Write, Edit
---
結果は、Write は使えず、Bash は使えて、echo b > written-deny-bash.txt でファイルが作られました。 disallowedTools は、継承したツールの一式から指定したものだけを引く仕組みなので、Bash などは残ります。公式ドキュメントにも、同じ disallowedTools: Write, Edit で「書き込み系を除いて継承する」例(no-writes)が載っています(Bash が残ることは、上の実機で確かめました)。
読み取り専用にしたいなら、拒否リストで引くより、tools: Read, Grep, Glob のように許可リストで必要なものだけを書くほうが、取りこぼしが出ません。両方書いた場合は、disallowedTools が先に適用され、残ったものに対して tools が解決され、両方に書いたツールは外れる、と公式ドキュメントにあります。
tools のツール名を間違えると、起動を拒否される
tools: Reed, Grap と、ツール名をわざと間違えた定義を呼んでみました。
Agent 'notes-typo' would be spawned with zero tools — refusing. Its tools list resolved to nothing: unrecognized [Reed, Grap]. Fix the agent's tools frontmatter or pass a different subagent_type.
ツールが1つも解決できないときは、起動自体が拒否されます。「間違えたせいで権限が無制限になる」ことはなく、安全側に倒れます。ただし、一部だけ間違えたときの挙動は試していません。公式ドキュメントも「たいていは拒否する(usually refuses)」という書き方です。
Bash を入れた時点で、読み取り専用ではない
調査用のエージェントに、git log やスクリプトの実行のために Bash を入れることはよくあります。ただし Bash があると、リダイレクトで書けますし、git commit もできます。Bash を渡すなら、そのエージェントは「読み取り専用」ではありません。本文の「読み取り専用」は、あくまでお願いです。自分の運用でも、定義の文章は「読み取り専用」なのに Bash を持っているものがあり、今もそこはツールで強制できていません。
Bash を渡して書き込みだけ止めたい場合は、tools: ではなくフックなど別の仕組みが要ります。この記事では扱いません(フックで止める手順は1本目の記事にあります。止まる範囲と、すり抜ける穴も書いています)。
自分が使っている形(縮約)
運用では、書き込む必要のあるエージェントだけが Write / Edit を持ち、調べるだけのエージェントは Read と Grep 中心にしています。形はこれだけです。
---
name: researcher
description: 実データを読んで分析する担当。調べるだけで、変更はしない。
tools: Read, Grep, Glob, WebFetch
model: sonnet
---
(役割・見るもの・出力の形を本文に書く)
model: で sonnet / opus / haiku などを指定できます。本文は役割と手順、tools: は権限、と役割を分けて書くと、あとで見直しやすくなります。
後片付け
テスト用ディレクトリは、検証が終わったら消します。~/.claude/agents/ に置いた自分の定義は、この手順では触っていません。
まとめ
- サブエージェントは
.claude/agents/<名前>.md。必須はnameとdescription -
tools:を省略すると、全ツール(MCP ツールを含む)を継承する。本文に「読み取り専用」と書いても、それはお願いでしかない -
tools: Read, Grepと許可リストで絞ると、Write と Bash は手札に渡されない(拒否ではなく、そもそも無い) -
disallowedTools: Write, Editだけでは、Bash から書ける。読み取り専用にしたいなら許可リスト - ツール名のタイプミスで解決できる名前が0になると、起動を拒否される
関連: