「is not matched by file permission checks」の対処法(Claude Code)
Write・NotebookEdit・Glob宛てのpermissionルールが効かないis not matched by file permission checks警告の直し方を解説します。書き換え先はEditとReadの2つです。
このTipsでできること
起動時のstderrにis not matched by file permission checksという警告が出たら、.claude/settings.jsonなどに書いたWriteやNotebookEdit宛てのパスルールは、一度も参照されていません。警告の意味と、EditとReadのどちらへ書き換えるかを扱います。書き換えなくてよいルールの見分け方と、書き換えたあとにパスの書き方でつまずく点もあわせて説明します。
なぜ「is not matched by file permission checks」が出るか
Claude Codeがファイルへのアクセスを許可・拒否・確認するかどうかを判定するとき、参照するのはEdit(path)とRead(path)の2種類のルールだけです。Write(path)・NotebookEdit(path)・MultiEdit(path)(廃止済みのツール)・Glob(path)のようなパス付きルールは、Claude Codeが保持はするものの、ファイル権限の判定には使いません。書き手の意図と実際の判定がずれているルールを見つけるための警告です。
Permission deny rule (.claude/settings.json): Write(docs/**) is not matched by file permission checks — only Edit(path) rules are. Use Edit(docs/**) instead (Edit rules cover all file-editing tools).警告にはルールの内容、出典(どの設定ファイルか)、書き換え先が示されます。この警告はv2.1.210以降で出ます。それより前のバージョンでは、無効なルールが警告なしに受理されていました。
書いたつもりと、実際の判定
Write(docs/**) を deny に書いた
設定ファイルには残りますが、ファイル権限の判定では参照されません。docs/配下への書き込みを止めているつもりでも、止まっていません。
Edit(docs/**) を deny に書いた
ファイルを編集する組み込みツールすべてに適用されます。WriteもNotebookEditも、このルールで判定されます。
この差が効いてくるのは、長く使い回しているsettings.jsonです。以前に「ドキュメントは書き換え禁止にしたい」とWrite(docs/**)のdenyを足し、そのまま見直していない設定では、v2.1.210より前は警告が出ないので、docs/配下が実際には無防備なまま動いていた可能性があります。アップグレード後にこの警告が出たなら、表記ゆれの指摘ではなく、効いていなかった保護ルールが初めて見えたと受け取るのが安全です。
書き換え先の対応表
ファイルを対象にした権限ルールは、ツール名にかかわらず次の2つへ集約します。
| 書いてしまいがちなルール | 書き換え先 | 理由 |
|---|---|---|
Write(path) | 書き換え先Edit(path) | 理由Editルールはファイルを編集するすべての組み込みツールに適用される |
NotebookEdit(path) | 書き換え先Edit(path) | 理由同上 |
MultiEdit(path)(廃止済みツール) | 書き換え先Edit(path) | 理由同上 |
Glob(path) | 書き換え先Read(path) | 理由読み取り系のルールはReadに統一されている |
{
"permissions": {
"deny": [
"Edit(docs/**)"
]
}
}--allowedToolsに渡したGlobルールだけは例外で、警告が出ません。それ以外の組み合わせは、すべてEditかReadへの書き換えが必要です。
書き換えなくてよいケース
警告の対象はパス付きのルールです。パスを持たない裸のツール名ルールには出ません。たとえばWriteという名前だけのdenyルールは、ツールレベルでそのまま一致判定されるので、書き換えは要りません。
- 書き換え不要:
"Write"(パスなし。ツール全体を対象にする) - 書き換えが必要:
"Write(docs/**)"(特定のパスを対象にする)
書き換えたあとに効くパスの書き方
Write(docs/**)をEdit(docs/**)に変えても、それだけでは意図どおりに止まらないことがあります。パスの解釈は、ルールの書き出しと、denyかallowかで変わるためです。EditとReadはどちらもgitignore形式のパターンで、次の4つの書き出しを区別します。
| 書き出し | 起点 | 例 |
|---|---|---|
//path | 起点ファイルシステムのルート | 例Read(//Users/alice/secrets/**) |
~/path | 起点ホームディレクトリ | 例Read(~/Documents/*.pdf) |
/path | 起点設定ファイルの置き場所を基準にした相対 | 例Edit(/src/**/*.ts) |
pathまたは./path | 起点現在のディレクトリ | 例Read(*.env) |
つまずきやすいのは、/始まりのEdit(/docs/**)が「ファイルシステム直下の/docs/」ではない点です。プロジェクトの設定に書けば、作業ディレクトリ直下のdocs/を指します。ユーザー設定に書いたRead(/secrets/**)も同じ理屈で、プロジェクトのsecretsではなく~/.claude/secrets/**をブロックします。どのプロジェクトにも効かせたいルールは、//か~/で書きます。
v2.1.211以降、.claude/settings.local.jsonはリポジトリルートに保存されます。ただし、ローカル設定のルールの起点は、リポジトリルートではなくセッションの主作業ディレクトリです。worktreeのセッションでEdit(/src/**)と書けば、そのworktree自身のsrc/が対象になります。
もう一つ、docsのようにセグメントが1つのディレクトリパターンは、ルールの種類で深さの扱いが変わります。
| ルール | src/app.tsに一致 | vendor/pkg/src/lib.jsに一致 |
|---|---|---|
allowのEdit(src/**) | src/app.tsに一致する | vendor/pkg/src/lib.jsに一致しない |
deny・askのEdit(src/**) | src/app.tsに一致する | vendor/pkg/src/lib.jsに一致する |
どの種類でもEdit(/src/**) | src/app.tsに一致する | vendor/pkg/src/lib.jsに一致しない |
どの種類でもEdit(**/src/**) | src/app.tsに一致する | vendor/pkg/src/lib.jsに一致する |
denyは深い階層のコピーまで巻き込み、allowは直下だけを許します。allowで深さを問わず許可したいときはEdit(**/src/**)と書きます。denyはこの差のおかげで書き漏らしにくい反面、意図しない場所まで止める可能性があります。
シンボリックリンクの扱いも、allowとdenyで非対称です。allowは、指定されたパスと解決後の実ファイルの両方が一致したときだけ適用されます。denyは、どちらか一方が一致すれば適用されます。たとえばRead(./project/**)をallow、Read(~/.ssh/**)をdenyにしているとき、./project/keyが~/.ssh/id_rsaを指すシンボリックリンクなら、読み取りはブロックされます。
除外の書き方も押さえておきます。denyやaskのパターンを!で始めると、gitignoreの否定になり、それより前に並べたルールから一部のパスを除けます。同じdenyリストでRead(*.env)のあとにRead(!sample.env)を置くと、sample.envだけを除いて、名前が.envで終わるファイルを深さを問わずブロックします。!のルールを先頭に置いても、何も除外されません。
否定には制約が3つあります。1つ目は、除外できるのは同じ設定元のルールだけという点です。プロジェクト設定や--disallowedToolsに書いたRead(!.env)は、管理設定が出したRead(./.env)のdenyを取り消せません。2つ目は、ディレクトリごとブロックしているルールの内側を、あとから開けられない点です。Read(secrets/**)とRead(!secrets/public/**)を並べても、secrets/publicはほかの部分と一緒にブロックされたままです。3つ目は、!の後ろに/・~/・//を付けても、現在のディレクトリ相対で読まれる点です。Read(!~/notes/public/**)は、Read(~/notes/**)から何も除外しません。
EditのルールはBashの出力リダイレクトにも及びます。>・>>・2>の書き込み先は、Editのallow・denyルールと保護パス、作業ディレクトリで確認されます。Write(docs/**)のつもりで書いた無効なルールは、echo x > docs/a.mdのようなBash経由の書き込みも止めていなかったことになります。書き換えてEdit(docs/**)にすれば、この経路にも同じ判定が働きます。
パスに括弧やブラケットが入っていても、書き方は変わりません。括弧はエスケープ不要で、Edit(./Finance (2024)/**)はFinance (2024)フォルダーをそのまま指します。一方、確認画面で「Yes, and don't ask again」を選んだときに保存されるルールは、[・]・*のようなgitignoreの記号がエスケープされ、承認したパスだけに一致します。手で書いたルールはエスケープされないので、[2024-06] Reportsのようなフォルダー名を手書きすると、意図しない兄弟ディレクトリまで巻き込むことがあります。この自動エスケープはv2.1.202からで、それ以前は記号がそのまま保存され、生成されたルールが自分のパスに一致しないことがありました。
Readのdenyは、GrepとGlobにも効きます。この2つはpath引数が解決するディレクトリを検索するので、Claude CodeはそのディレクトリにReadのdenyを当てます。Glob(scripts/**)のように検索側のツール名でルールを書く必要はなく、Read(scripts/**)だけで足ります。
EditとReadが実際にカバーする範囲
書き換え先のEditとReadは、対象範囲もツール名から想像するより広くなっています。Editルールはファイルを編集する組み込みツールすべてに適用されます。Readルールは組み込みの読み取りツールに加え、GrepやGlobでの検索、プロンプト中の@fileメンション、接続したIDEが共有する選択範囲や開いているファイルにも、ベストエフォートで適用されます。個別に書いていたWrite(path)やGlob(path)を集約すると、実際の挙動と設定ファイルの見た目が一致します。
副作用にも注意が要ります。Readのdenyルールは、同じパスへのEdit・Writeも止めます。新規ファイルの作成も含みます。ただしNotebookEditは対象外なので、ノートブックも含めて書き込みを禁止したいパスには、Readのdenyとは別にEditのdenyも必要です。この動作は編集側がv2.1.208以降、書き込み側がv2.1.228以降なので、それより古いバージョンではReadのdenyだけでは保護が働きません。
出典の読み分けと直す場所
警告は出典を丸括弧の中に示します。どこを直すかは、この出典で決まります。
出典欄の4パターン
通常のファイルパス
.claude/settings.jsonのような実在するファイルです。そのファイルを開いて、該当ルールを書き換えます。claude-settings-<hash>.json
ディスク上に実在しない名前です。
--settingsフラグにインラインで渡したJSON値を指すので、コマンドラインに渡しているJSON文字列を直します。フラグ名そのもの
--allowedToolsや--disallowedToolsが出典に出た場合です。設定ファイルを探さず、実行スクリプト側の引数をEdit(path)などへ書き換えます。managed policy settings
組織の管理者が配布した設定なので、自分では直せません。警告の内容を管理者に伝えて、配布元のルールを直してもらいます。
managed policy settingsは組織全体に配られる設定なので、同じ警告が多数のメンバーの環境に出ている可能性があります。個別に対処するより、配布元を1か所直すほうが早く片づきます。
--disallowedToolsに渡したルールでも同じ警告が出る
この警告は設定ファイルだけでなく、--allowedTools・--disallowedToolsにパス付きルールを直接渡したときにも出ます。CIの実行スクリプトにルールをインラインで書いていると、設定ファイルを探しても見つからず戸惑いますが、原因は同じです。
claude -p "docs/READMEを更新して" \
--disallowedTools "Write(docs/**)"このWrite(path)も、設定ファイルに書いた場合と同じ理由で参照されません。警告の出典欄にはフラグ名が出るので、引数をEdit(path)に直します。
無人実行では、警告の出力先にも気をつけます。バックグラウンドセッションや--output-format json・stream-jsonでは、機械が読む出力を汚さないため、警告はstderrに出ません。代わりに~/.claude/debug/<session-id>.txtのデバッグログへ書かれます。この構成のCIでは、--debugを付けて実行しない限り警告に気づけません。
見た目が似ている別の警告と混同しない
Bash(command:rm *)のように、ツールの主要な入力フィールドをparam:value形式で指定したときにも、起動時に警告が出ます。複合コマンドで回避できてしまうので、Claude Codeがルールを無視するためです。対象になる主要フィールドは、ツールごとに決まっています。
| ツール | マッチできない主要フィールド |
|---|---|
| Bash / PowerShell | マッチできない主要フィールドcommand |
| Read / Edit / Write | マッチできない主要フィールドfile_path |
| Grep / Glob | マッチできない主要フィールドpath |
| NotebookEdit | マッチできない主要フィールドnotebook_path |
| WebFetch | マッチできない主要フィールドurl |
直し方は、Bash(rm *)、Read(./path)、WebFetch(domain:host)のように、各ツール本来の指定子へ書き直すことです。ファイル系ツールの名前そのものが判定に使われない今回の警告とは原因が別なので、警告文がどちらの型かを読み分けてから手を付けます。
書き換え前後の設定を見比べる
docs/の書き換え禁止とscripts/の読み取り許可の例を、.claude/settings.jsonで見比べます。書き換え前は、WriteとGlobの2種類のツール名でパスを指定していますが、どちらも判定に使われていません。
{
"permissions": {
"deny": ["Write(docs/**)"],
"allow": ["Glob(scripts/**)"]
}
}書き換え後は、禁止をEdit、許可をReadに集約します。ツールの種類で分けていた発想を、「読むか、編集するか」という操作の種類で分ける形に置き換えます。permissionsブロック全体の書き方はsettings.json完全ガイドにあり、同種のずれを他の項目で探すときの手がかりになります。
{
"permissions": {
"deny": ["Edit(docs/**)"],
"allow": ["Read(scripts/**)"]
}
}書き換え後の確認
- 1
Claude Codeを起動し直す
警告は起動時に出るので、設定を保存しただけでは消えたかどうか分かりません。
- 2
同じ警告が出ないことを見る
stderrに同じ警告が出ないことを確認します。
- 3
ほかの警告が残っていないか見る
同じ起動時に、
command:型やBashのワイルドカードの警告が並んで出ることがあります。原因が別なので、1件ずつ直します。
v2.1.285で確認できた範囲
手元のclaude(v2.1.285)で、警告が指す2つのフラグの説明を確認しました。claude --helpの該当部分は次のとおりです。
--allowedTools, --allowed-tools <tools...>
Comma or space-separated list of tool names to allow (e.g. "Bash(git *) Edit")
--disallowedTools, --disallowed-tools <tools...>
Comma or space-separated list of tool names to deny (e.g. "Bash(git *) Edit")
--settings <file-or-json>
Path to a settings JSON file or a JSON string to load additional settings fromヘルプの例も、パス付きのWrite(...)ではなく、パスなしのEditです。警告文の全文は、公式のエラー一覧と一致します。
まとめ
is not matched by file permission checksは、denyのつもりで書いたルールが実は無効だったと知らせる警告です。ファイル系のパス付きルールはEditとReadだけに書き、裸のツール名は触らず、直す場所は出典欄に従います。書き換えるついでに、/始まりのパスの起点とdenyの深さも確認すると、保護が意図どおり効いているかを判断できます。