← ブログに戻る

Claude Codeサブエージェントの3階層権限

この記事を含む総合ガイド Claude Code 実戦運用ガイド

「サブエージェントに Read/Ask/Do の 3階層権限を実装した」と書き出すつもりでした。実物の ~/.claude/settings.json を数え直したら、allow が 25 個、askdeny は 0 個。3階層のつもりが、蓋を開けたら 1階層 + auto の投げっぱなし でした。

この記事は、Claude Code の権限スキーマを 公式ドキュメント で再確認し、私の実運用の答え合わせをした記録です。前回この題材で書いた記事は、図と本文の主張が真逆になっていて自分で消しました。今回はまず「事実がどうなっているか」から書きます。

権限は settings.json 側とサブエージェント側の2箇所にある

Claude Code の権限まわりで最初に混乱するのがここです。私も混乱しました。整理すると2箇所あります。

  • ~/.claude/settings.jsonpermissions ブロック: セッション全体 (メインもサブエージェントも共通) に効く承認ルール
  • サブエージェント定義ファイル (.claude/agents/<name>.md) の frontmatter: そのサブエージェント個別 のツール絞り込み

前者が承認要否 (allow / ask / deny) を決め、後者はアクセスできるツールの allowlist を決めます。役割が違います。

settings.json 側: allow / ask / deny の3配列

公式ドキュメントの permissions ページ にそのままの形で書かれています。

{
  "permissions": {
    "allow": ["Bash(git status)", "Bash(rg:*)"],
    "ask":   ["Bash(git push:*)"],
    "deny":  ["Bash(rm -rf:*)"],
    "defaultMode": "auto"
  }
}
  • allow: 該当したら承認プロンプトを出さず即実行
  • ask: 該当したら毎回承認プロンプトを出す
  • deny: 該当したら実行そのものを拒否
  • defaultMode: 3配列のどれにも該当しなかったときの既定挙動 (default / acceptEdits / auto / dontAsk / bypassPermissions / plan)

3配列の記法は Bash(git add:*) のように「ツール名(パターン)」。ここまでは仕様の話。

サブエージェント側: tools と disallowedTools

サブエージェント定義の frontmatter は、しばしば allowed-tools: と書かれた例をネット上で見かけます。これは間違いです。公式のサブエージェント仕様 では以下の名前です。

---
name: reader
description: Read-only investigator
tools: Read, Glob, Grep
disallowedTools: Write, Edit
model: sonnet
permissionMode: default
---
  • tools: カンマ区切りの allowlist。ここに書かれたツールだけ使える
  • disallowedTools: 同じくカンマ区切りの denylist
  • permissionMode: このサブエージェント個別の既定モード

tools は「アクセスできるツールの絞り込み」であって、承認要否 (allow / ask / deny) を決めるものではありません。ここが2つ目の混乱ポイントでした。承認要否は settings.json 側で決まる。サブエージェント側の tools は「そもそも触れないようにする」処置。

私の実物 settings.json を数えた

19日前 (2026-08-11 最終更新) の私の ~/.claude/settings.json を、いま数え直しました。

allow:       25 個
ask:          0 個
deny:         0 個
defaultMode: "auto"

allow 25個の中身をカテゴリで切ると次のとおりです。

  • git 系 8 個: git status / git diff:* / git log:* / git show:* / git branch:* / git add:* / git commit:* / git stash:*
  • 閲覧・調査系 15 個: ls:* / pwd / cat:* / head:* / tail:* / wc:* / file:* / which:* / echo:* / date / tree:* / jq:* / rg:* / grep:* / find:*
  • Web 系 2 個: WebSearch / WebFetch

Read/Ask/Do 3階層のうち、埋めていたのは1階層だけ。allow 25 パターン、ask と deny は 0

気づいた3つのズレ

数えてみて、事前に頭の中で描いていた「Read/Ask/Do 3階層」との差が3つありました。

1. 書き込み系も allow に置いていた

git add:*git commit:* は allow に入っています。「Read 相当は allow、Write 相当は ask」の原則からすると、この2つは ask に落とすべきでした。実際には落としていない。理由は単純で、ローカルの feature ブランチにコミットするたびに承認プロンプトが出るのが煩わしくて、ある日「もういい」と allow に上げたからです。CI ゲートがない個人リポでは、ローカル commit を確認しても得るものが薄い。事故は git push --force で起きるので、そちらを deny に書けば済む話でした (書いていませんが)。

2. ask 節を1行も書いていなかった

3階層のうち中間層に何も入れていない。理論上 ask は「毎回聞かれるが、その分事故が減る」帯として機能するはずですが、私の運用ではその帯が空でした。理由は、allow に入っていない操作は defaultMode: auto の判定サービスが自動で「聞くべきかどうか」を決めてくれるので、明示的に ask を書かなくても回っていた、から。書かなくて済むと書かない。

3. deny 節も空

危険コマンド (rm -rf /git push --force origin main など) を明示拒否していない。これも defaultMode: auto の判定に任せていました。

auto の判定サービスが落ちた日

defaultMode: auto の裏側では、Anthropic 側のツール安全判定サービスに毎回問い合わせが飛んでいるらしい、と気づいたのは 7月18日の障害からでした。10分ほどそのサービスが落ちて、書き込み系の Bash 呼び出しが全部滞留しました。私はその場で auto を解除して、読み取り作業に切り替えて時間をつないだ記憶があります。

このとき動き続けたのは、settings.json の allow に明示的に書いてあったコマンドでした。判定サービスへの問い合わせを飛ばさず、ローカルの allowlist だけで通す経路が残っていた形です。ここで初めて allow を書くことの副次的な意味を実感しました。allowlist は「聞かれないため」ではなく「外部サービスに依存しないため」でもあるという話です。

サブエージェント個別に権限モードを変えたいとき

セッション全体は settings.json ですが、「このサブエージェントだけ auto、あのサブエージェントは毎回聞く」を実現したい場面もあります。そのときは frontmatter の permissionMode が使えます。

---
name: db-writer
description: 本番DBに書き込むサブエージェント
tools: Bash, Read
permissionMode: default
---

permissionMode: default は「settings.json のルールに従い、該当しないものは毎回聞く」既定モードです。auto を効かせたいところだけ auto に、聞かせたいところだけ default にできます。加えて hooksPreToolUse に個別のシェルスクリプトを噛ませれば、条件付きの承認ゲートを書けます。

このあたりの「hook で条件付きゲートを書く」実装は、以前 MCP承認ゲートを3層に分けた にまとめました。今回の記事は静的宣言 (settings.json) の話、あちらは動的判定 (hook) の話、と分担する形で読めます。設計論としてのサブエージェントの使い分けは Claude CodeのSub-agent設計 に、記憶の分離は サブエージェントにメインの記憶を渡すのは事故だった にまとめてあります。

これから足すもの

答え合わせをした結果、私が最初に足すのは deny 節です。

"deny": [
  "Bash(rm -rf /:*)",
  "Bash(rm -rf ~:*)",
  "Bash(git push --force:*)",
  "Bash(git push -f:*)"
]

ask 節はしばらく空のままにする予定です。書きたくなる契機がまだ来ていないので、来てから書きます。allow の書き込み系 (git add:* / git commit:*) は、いまの個人リポ運用では ask に落とす利益が小さいので現状維持。CI ゲートのないチームリポに参加するときだけ、そのリポの .claude/settings.json (プロジェクトローカル) に上書きで ask を入れる、という2階建てを想定しています。

締め

「Read/Ask/Do 3階層を実装した」と書きたかったのに、事実は「Read だけ 25 個ぶん実装して、Ask と Do は放置していた」でした。標語より実物を数えるほうが正直です。ここから足す deny の4行が、本当に事故を止めてくれるかどうかは、次に rm -rf を打ち間違えた日にだけ分かります。


本記事の題材である Claude Code の設定運用を体系的にまとめた電子書籍は 実践Claude Code にあります。

実践Claude Code 関連書籍 実践Claude Code 毎日 Claude Code を使うエンジニアへ — CLAUDE.md・Plan Mode・チーム展開の実践ガイド 書籍ページを見る →