Claude Codeサブエージェント権限ルール設計
Claude Codeのサブエージェントに親セッションと異なる権限を持たせる設計を、tools/disallowedTools・permissionMode・Agent許可リスト・PreToolUseフックの4つの仕組みから解説します。
サブエージェントは独立したコンテキストウィンドウで動くだけでなく、独立した権限設定を持てます。親セッションが書き込み自由でも、子は読み取り専用に絞る。親では使わせたくないMCPサーバーだけ、子に持たせる。こうした設計が可能です。
ただし「子に書いた制限が効かない」「子に許可したはずのツールが消える」といった相談は、権限が1か所で決まっていないことから起きます。本記事は権限設計そのもの、つまり何が親から引き継がれ、どこを上書きできて、どこは上書きできないかを扱います。
auto modeで子の最終報告がどう審査されるかはSubagentHandbackとはにあります。作り方や並列実行はClaude Code Sub-agents完全ガイドとサブエージェント並列実行パターンを参照してください。
サブエージェントの権限は3か所で決まる
子の権限は、独立した3つの仕組みの合成です。1つを直しても別の仕組みが結果を決めていることがあるため、症状が出たら先にどの層の問題かを切り分けます。
権限を決める3つの層
ツールプール
toolsとdisallowedToolsで、子が呼べるツールの範囲そのものを決めます。プールに無いツールは、許可ルールがあっても呼べません。承認モード
permissionModeで、プールに残ったツールを使うときの確認の求め方を決めます。ただし親のモードによっては無視されます。ルールとフック
permissionsのdenyルールやPreToolUseフックで、同じツールの中でも引数に応じて許可と拒否を分けます。settings.json側の設定は、親と子の両方に効きます。
子に渡るツールが決まるまでの順序
子は既定で、親の会話が使える組み込みツールとMCPツールを引き継ぎます。実際に手元へ届くまでには、次の順序で絞り込みが入ります。
子のツールセットが決まる順序
- 1
フォークかどうかを判定する
フォーク(現在の会話をそのまま複製する起動方式)は、以降のフィルタをすべて飛ばして親と同じツールプールを受け取ります。
- 2
全サブエージェント共通の除去
toolsに書いても、AskUserQuestion、EnterPlanMode、ScheduleWakeup、WaitForMcpServers、Workflowは除かれます。ExitPlanModeはpermissionModeがplanのときだけ残ります。EndConversationも除かれ、会話を終えられるのはメインの会話だけです。深さ上限に達した子からはAgentも消えます。 - 3
バックグラウンド実行の追加フィルタ
既定の実行形態であるバックグラウンドでは、MCPツールはすべて維持され、組み込みツールは
ReadGrepGlobLSPBashPowerShellEditWriteNotebookEditWebFetchWebSearchTodoWriteSkillToolSearchEnterWorktreeExitWorktreeMonitorTaskStopSendMessageArtifactの20個に絞られます。SubagentHandbackで最終報告を返す子には、このツールも加わります。 - 4
disallowedToolsを先に、toolsを後に適用
残ったプールから
disallowedToolsを引き、そのあとでtoolsが絞り込みます。両方に書いたツールは除かれます。
同じ定義でもフォアグラウンドとバックグラウンドでツールセットが変わる点は、次の節のtoolsの書き方にも響きます。バックグラウンドではLSPがv2.1.280から使えるようになったので、それ以前のバージョンでは20個ではなく19個でした。
WebSearchの回数上限を変える設定はCLAUDE_CODE_MAX_WEB_SEARCHES_PER_SESSIONで扱っています。
書き込みを止めたいときのtools/disallowedTools
書き込みだけ止めたい、調べものに専念させたい、という場面の起点はtools(許可リスト)とdisallowedTools(拒否リスト)です。toolsを書くと、リストに無いツールは使えなくなります。
---
name: safe-researcher
description: 調査専任、書き込み不可
tools: Read, Grep, Glob, Bash
---この例ではWrite・Edit・MCPツール全般が使えません。逆に、継承済みのプールから一部だけ除きたいときはdisallowedToolsを使います。
---
name: no-writes
description: 継承したツールプールからWrite/Editだけ除く
disallowedTools: Write, Edit
---MCPサーバーはサーバー単位でもまとめて指定できます。mcp__<サーバー名>かmcp__<サーバー名>__*でそのサーバーの全ツールを対象にし、disallowedToolsのmcp__*は全サーバーのMCPツールを一括で除きます。
---
name: local-only
description: githubサーバーのツールだけ使わせない
disallowedTools: mcp__github
---toolsのエントリーが1つも解決できないと(スペルミスや、子では使えないツール名など)、Claude Codeは通常その子の起動を拒否し、解決できなかった名前を含むエラーを返します。直し方は「spawned with zero tools」の対処にまとめています。
Bashのうち特定のコマンドだけ止めたいとき
disallowedToolsにはコマンド単位の指定を書けません。disallowedTools: Bash(git push *)と書いても、除かれるのはBashツール全体です。git pushだけを止めてgit statusは通したい場合、disallowedToolsは使えないことになります。
代わりに、settings.jsonのpermissions.denyへBash(git push *)を書きます。このルールは親の会話と子の両方に適用されます。子だけに限定したいときは、後述するPreToolUseフックを使います。
子の承認モードは親のモードに負けることがある
permissionModeは、承認の求め方を子ごとに変えます。設定できる値はdefault(都度確認)、acceptEdits(編集の自動承認)、auto(バックグラウンドの分類器が判定)、dontAsk(明示的に許可した以外は自動拒否)、bypassPermissions(確認の全スキップ)、plan(読み取り専用の調査)の6つです。manualはdefaultの別名で、v2.1.200以降で書けます。
未設定なら親のモードを引き継ぎます。指定した値が使われるかどうかは、親のモードで決まります。
子のpermissionModeが効く親、効かない親
効く親のモード
子の指定どおりに動きます。ただしbypassPermissionsだけは例外で、子が宣言しても親のモードのままです(v2.1.267以降)。
無視される親のモード
子は親と同じモードで動き、frontmatterの指定は無視されます。autoの親では、分類器が親と同じブロック/許可ルール(auto modeの分類器ルール)で子のツール呼び出しを評価します。子の作業が終わると、分類器は最終報告も審査します。
permissions.disableBypassPermissionsModeが"disable"になっている場合、子がpermissionMode: bypassPermissionsを宣言しても無視され、親のモードで動きます。この制限はv2.1.223より前には、エージェント定義側のbypassPermissionsで迂回できる穴がありました(同バージョンで修正)。この設定は管理設定に置くのが一般的ですが、どの設定ファイルに書いても効きます。
バックグラウンドの子が確認を要する操作に当たると、確認は親のセッションに表示され、どの子が求めているかが示されます。ここで「セッション中はずっと許可」のように1回限りでない選択肢を答えると、その許可は親の会話を含むセッション全体に適用されます。子ひとつのつもりで押した許可が広がる点は、運用で見落としやすいところです。
どの子エージェントを起動できるかを絞る
子にさらに孫を起動させるかどうかも制御できます。claude --agentでメインスレッドとして動かしたエージェントなら、toolsにAgent(worker, researcher)と書いて起動できる種類を許可リスト化できます。
この形式は--agentのメインスレッドにだけ効きます。通常のサブエージェント定義でtoolsにAgentを含めても、括弧内の指定は無視され、深さ上限の範囲で孫を起動できるようになるだけです。孫を作らせたくない子(読み取り専用のレビュアーなど)は、toolsからAgentを外すかdisallowedToolsに加えます。
組織全体で特定のサブエージェントを止めたいときは、settings.jsonのpermissions.denyにAgent(名前)を書きます。組み込みにも自作にも効きます。
{
"permissions": {
"deny": ["Agent(Explore)", "Agent(my-custom-agent)"]
}
}同じ指定はclaude --disallowedTools "Agent(Explore)"のようにCLIフラグでも渡せます。許可リスト構文が起動できる子を絞るのに対し、denyは特定の子だけを止めます。全サブエージェントを止めるならAgentツールそのものをdenyします。指揮系統を組む設計はClaude Codeオーケストレーター設計で扱っています。
引数に応じて許可を分けるPreToolUseフック
toolsとdisallowedToolsは静的な許可・拒否です。「Bashは許可するがSQLの書き込み文だけは止めたい」のような分岐は、hooksフィールドのPreToolUseで書きます。
---
name: db-reader
description: 読み取り専用のDBクエリだけ実行する
tools: Bash
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./scripts/validate-readonly-query.sh"
---フックはBashが呼ばれるたびにスクリプトへJSONを渡し、スクリプトが終了コード2を返すと呼び出しはブロックされます。INSERTやUPDATEなどを正規表現で検出して止める、といった粒度はtoolsでは表せません。スクリプトにはJSONが標準入力で届きます。macOSとLinuxでは実行権限が無いとフックが失敗し、何もブロックしません。スクリプト全文と動作確認はサブエージェントを読み取り専用のDB分析役にするhookにあります。
フックの置き場所によって効く範囲が変わります。
- frontmatterのフック: その子が動いている間だけ有効です。プロジェクトの
.claude/agents/にある定義は、そのフォルダを信頼するまで発火しません(v2.1.218以降)。-pセッションは信頼済みと扱われません - settings.jsonのフック: 親と子の両方のツール呼び出しで発火します。子だけに限定するなら、frontmatterに置きます
- プラグインが配布する子:
hooksmcpServerspermissionModeの3つのfrontmatterは無視されます。必要なら定義ファイルを.claude/agents/か~/.claude/agents/へコピーします
MCPサーバーを子だけに接続する
mcpServersを使うと、親の会話には無いMCPサーバーを子だけに接続できます。インライン定義は子の起動時に接続され、終了時に切断されます。
---
name: browser-tester
description: Playwrightで実ブラウザを操作してテストする
mcpServers:
- playwright:
type: stdio
command: npx
args: ["-y", "@playwright/mcp@latest"]
- github
---インライン定義(playwright)と、親で設定済みのサーバーを名前だけで参照する形式(github)は混在できます。インライン側の利点は、ツール説明が親のコンテキストを消費しないことです。名前参照は親と同じ接続を共有します。
信頼の扱いも押さえておきます。プロジェクトの.claude/agents/にある定義のインラインサーバーは、そのフォルダを信頼するまで読み込まれません(v2.1.238以降)。一方、~/.claude/agents/の定義、--agentsで渡した定義、管理設定が配るものは、この確認なしで読み込まれます。
--strict-mcp-config、管理設定のallowedMcpServers / deniedMcpServersは、frontmatterで宣言したサーバーにも同じ制限がかかります(v2.1.153以降)。例外は--agentsやSDKのagentsで明示的に渡したインライン定義で、--strict-mcp-configでは除かれません。
v2.1.285のヘルプで確認できる範囲
v2.1.285でclaude --helpを実行し、権限まわりのフラグを確認しました。ネットワークやモデルを呼ばない範囲です。
$ claude --version
2.1.285 (Claude Code)
$ claude --help
--permission-mode <mode> Permission mode to use for the session
(choices: "acceptEdits", "auto",
"bypassPermissions", "manual",
"dontAsk", "plan")
--disallowedTools, --disallowed-tools <tools...>
Comma or space-separated list of tool names to deny (e.g. "Bash(git *)
Edit")
--agents <json-or-file> JSON object defining custom agents, or
with --print the path to a file that holds one--permission-modeの選択肢にはdefaultの代わりにmanualが並びますが、defaultを渡しても同じManualモードで動きます。--disallowedToolsの例にはBash(git *)のようなコマンド単位の指定が載っています。これはセッション全体の拒否ルールとして働くフラグで、子のfrontmatterにあるdisallowedToolsとは別物です。子のfrontmatterでは同じ書き方がBash全体の除外になります。
作業場所を切り離すisolation: worktree
権限を絞る方法とは別に、子の作業場所そのものを分ける手段もあります。isolation: worktreeを指定した子は、一時的なgit worktree(リポジトリの隔離コピー)で動きます。変更が無ければ、worktreeは自動で片付けられます。
子のBashとPowerShellのコマンドは、このworktreeの中で実行されます。作業ディレクトリが親のチェックアウトに解決されるコマンドはエラーになります。この判定はv2.1.203からで、それより前は親のチェックアウトで実行されることがありました。
子の中でcdしても、次のBashやPowerShellの呼び出しには持ち越されません。親の作業ディレクトリにも影響しません。書き込みを許す子でも、書き込み先を別のコピーに閉じ込められます。
「制限」より「切り離し」で設計する
ここまでの仕組みを並べると、とりあえず全部絞る設計に倒れがちです。ですが調査専任の子に書き込み権限を与えないのは、信用していないからではありません。その責務に書き込みが要らないからです。境界を明確にするほど、親の判断も単純になります。
実務では、toolsによる静的な絞り込みを基本線にします。PreToolUseフックは、静的なリストでは書けない分岐が要るときだけです。フックを重ねると、どの権限がどこで決まっているかを追うコストが、権限設計そのものの複雑さを上回ります。
よくあるつまずき
disallowedTools: mcp__githubなのに一部のGitHubツールが残る:mcp__github(またはmcp__github__*)はサーバー全体、mcp__github__特定ツール名は個別ツールだけが対象です。書き間違いが無いか確認します- 子から
Skillを使わせたくない:skillsフィールドを空にしても、実行中のSkillツール呼び出しによる発見と起動は防げません。toolsからSkillを外すか、disallowedToolsに加えます。disable-model-invocation: trueのSkillは、skillsでのプリロードもできません
まとめ
子の権限を設計するときは、ツールプール、承認モード、ルールとフックのどの層で止めたいかを先に決めます。親がacceptEdits / bypassPermissions / autoなら承認モードは親に従うので、子だけ厳しくしたい場面ではツールプール側で絞ります。