Windows で Claude Code を使っていて、settings.json の deny に git rebase のような行を置いている人に向けて書く。2026年8月4日に出た v2.1.221 で、PowerShell の権限チェックが引用符を含むパスを取り違える不具合が直った。取り違えの向きが過剰遮断だったのか素通りだったのかはリリースノートに書かれていない。素通り側だったなら、書いたはずの deny が書いたとおりに読まれていない版を使っていたことになる。筆者は Windows 11 と PowerShell で毎日 Claude Code を回しているので、上げて終わりにせず、deny の書き方を変えて確かめる手順のほうを先に組んだ。
目次
v2.1.221 のうち、運用に触る4項目
リリースノートには39行の変更が並んでいる。本稿で扱うのはそのうち4つで、下の日本語は筆者の要約になる。原文は出典のリリースノートを開いてほしい。
| 変わったこと | 効く環境 |
|---|---|
Focus view の追加。チャットメニューのトグルで、ツール実行のログをターン単位の折りたたみ要約に隠す(Ctrl+Alt+F・実行中インジケータつき) |
VS Code 拡張 |
| PowerShell の権限チェックが引用符を含むパスを取り違える不具合の修正。修正後は承認プロンプトが出る | Windows |
起動時のプロセス生成時刻の取得を、PowerShell の起動から kernel32 のネイティブ呼び出しへ変更。効能として挙がっているのは、powershell.exe をゲートするエンドポイントセキュリティ製品がプロンプトを出さなくなること |
Windows |
zsh が [[ ]] の正規表現条件に隠しコマンドを通せた Bash 権限チェックのバイパスを塞いだ。こちらも承認プロンプトが出る形になった |
zsh |
出典: Claude Code v2.1.221 リリースノート(GitHub・公式)
権限に触る変更はもう1つある。auto モードで並列ツール呼び出しの権限チェックがキャッシュ効率よくなり、チェック保留中にモードを切り替えても古い結果が適用されずプロンプトが出るようになった、というもの。auto モードを常用していないので本稿では扱わない。Windows 向けの修正も、上の2件のほかに Bedrock と AWS SSO 名前付きプロファイルまわりが1件ある。Bedrock を使っていないので同じく外した。
この表から先へ踏み込むには足りない情報がある。修正対象の PowerShell が Windows 標準の 5.1 なのか後入れの 7 系なのか、kernel32 のどの関数を呼ぶようになったのか。どちらも原文に書かれていないので、一次ソース側で埋める手段がない。読者が使っている版を確かめるなら claude --version か、セッション内の /status で足りる。
Focus view は VS Code 拡張の機能で、まず併存するかを確かめる
Focus view はツール実行のログを畳んで、ターンごとの要約だけを見せる。長い自動作業を回している間、画面が Read と Edit の羅列で埋まらなくなる。トグルなので Ctrl+Alt+F でいつでも戻せる。
ここで先に押さえておきたいのは、リリースノートの当該行が [VSCode] で始まっている点だ。コマンドパレットの項目名も VS Code 側のものになっている。筆者が自作の statusline を出しているのはターミナルの CLI で、statusline-combined.js という連結ラッパーがモデル名・コンテキスト使用率・プロジェクト名の行と、claude-hud の「実行中のツール・サブエージェント・TODO」を横に並べている。VS Code 拡張の Focus view と、ターミナルの statusLine が同じ画面に出るのかどうか自体が未確認になる。
そこで確認の順番を組み替えた。0点目として、そもそも両方が同じ画面に併存するのかを見る。併存しないなら、以降の3点は確かめる必要がない。
併存した場合に見るのが次の3点。畳んだ要約から個別のツール呼び出しへ戻れるか。実行中インジケータが statusline の表示と重複しないか。権限プロンプトが畳まれた要約の中に埋もれないか。いちばん知りたいのは3点目で、deny や ask が出る瞬間を見落とす作りなら常用しない。
気にしている理由がもう1つある。サブエージェントのネストを測る条件を決めた回で、下請けの下請けが落ちたときにどの段で何が起きたのか上まで届きにくい、と書いた。ログを畳む機能はその見えにくさと同じ方向へ働く。多段で回している最中に Focus view を常用するなら、失敗の追跡は headless の stream-json 側へ寄せることになる。
引用符入りパスは Windows だと日常的に出てくる
Windows のパスには空白が普通に混じる。C:\Program Files が代表格で、ユーザー名やクラウド同期フォルダにも空白が入りやすい。PowerShell へ渡すときは引用符で囲むか、囲まないなら別のエスケープが要る。
# 空白を含むパスは引用が要る
& "C:\Program Files\Git\bin\git.exe" status
# 引用符が入れ子になる形
Start-Process -FilePath "C:\Program Files\App\tool.exe" -ArgumentList '--out "D:\out dir\a.txt"'
権限チェックは、実行しようとしているコマンドの文字列を解析して、どのコマンドがどの対象に触るのかを判定する。PowerShell については、公式ドキュメントが「AST を解析して、複合コマンドの各コマンドを個別に検査する」と書いている。引用符の対応を取り違えると、この解析の結果がずれる。
貼り紙に例えると分かりやすい。deny は「この扉は開けるな」と書いて貼っておく紙だ。紙に書いた部屋の名前が「第 2 会議室」で、読む側が空白のところで切って「第」までしか見ていなかったら、その貼り紙は別の扉に貼ってあるのと変わらない。扉は開く。誰も違反した自覚を持たない。
allow 側と deny 側では、ずれたときの困り方が違う。allow がずれれば余計なプロンプトが増えるので、使っているうちに気づける。deny がずれると、止まってほしいものが黙って通る。画面には何も出ない。v2.1.214 で allow ルールの一致範囲が変わった回に書いたとおり、締める方向のルールは広く効き、緩める方向は狭く効くという非対称が設計として入っている。それでも、パスの読み取り自体がずれていれば非対称の前提から崩れる。
筆者のリポジトリは C:\Users\username\MY_Work_Space\BugattiAlpha_Hub で、アンダースコアを使って空白を避けてある。deny に置いてあるのも git rebase と git reset という、パスを含まない形。当たりにくい設定ではある。当たりにくいことと当たっていないことは違う。稼働中のサービスを止めないための protect-services フックが見ている kill 系のコマンドや、%LOCALAPPDATA%\Programs\ 配下の実行ファイルを呼ぶ場面では、引用符入りのパスがそのまま出てくる。
ルールの書式を先に確定させる
再現テストを組む前に、書式を1つ確定させておく必要があった。隔離の穴を筆者の環境で破れるか試した回と同じで、書式そのものを間違えたまま「止まらなかった」と結論を出すのが、この手の検証でいちばん起こしやすい失敗になる。
公式の権限ドキュメントを開いて分かったことが3つある。
1つ目。PowerShell には Bash とは別のルール名がある。ドキュメントは「PowerShell の権限ルールは Bash ルールと同じ形を使う」と書いたうえで、例を PowerShell(Get-ChildItem *) の形で挙げている。Windows で PowerShell のコマンドを止めたいなら、書くのは Bash(...) ではなく PowerShell(...) になる。ここを取り違えたまま組むと、塞いだつもりのないツールを塞いで、狙ったツールは素通りする。
2つ目。ワイルドカードは位置を選ばない。* は先頭でも中間でも末尾でも書ける。:* は末尾の * と同じ意味で、末尾でだけワイルドカードとして解釈される。エイリアスは正規化されてから照合され、大文字小文字は区別しない。
この2つ目が効いてくる。テストで実行させたいのは & "D:\perm probe\probe.cmd" という形で、文字列は & から始まる。パスを先頭に置いた前方一致では引っかからない見込みになる。ワイルドカードを前後に置いて位置に依存しない形にすれば、この問題を避けられる。
3つ目。解析しきれないコマンドは、素通りではなく承認プロンプトへ倒れる。ドキュメントに「Claude Code がコマンドを完全に解析できないときは、読み取り専用として扱わず承認を求める」と書いてある。今回の修正後の挙動がプロンプトなのは、この既定の受け皿に乗った形になる。10,000文字を超えるコマンドも常にプロンプトになる。
deny を書くときに知っておくと効く仕様が、ほかに3つある。
- ラッパーは剥がされてから照合される。
timeout/time/nice/nohup/stdbuf、シェル組み込みのcommand/builtin、zsh のnoglob、フラグ無しのxargsが対象。Bash(npm test *)はtimeout 30 npm testにも当たる - deny は先頭の環境変数代入を跨いで当たる。
Bash(rm *)の deny はFOO=bar rm -rf tmp/にも当たる。allow 側は既知の安全な変数を除いて跨がない - ラッパーの一部は前方一致で許可できない。
watch/setsid/ionice/flockと、findの-exec/-deleteは常にプロンプトになる。devbox runやdocker execのような実行系ランナーは剥がされないので、Bash(devbox run *)は中身が何であっても通ることになる
最後の1つは deny ではなく allow の話だが、書き方を間違えると穴になる向きは同じだ。
deny ルールで再現テストを設計する
確かめたいのは、引用符で囲んだパスを含むコマンドが deny でどう判定されるかだ。引用符の種類、呼び出し演算子の有無、空白を含まないパス、相対パスに変えたときに判定が変わるかを記録する。
本番の deny は触らずに済ませたい。git rebase と git reset の deny は履歴を守る安全網で、検証のために外したり書き換えたりすれば、その間だけ穴が開く。検証用のディレクトリとプロジェクトを別に作って、そこの .claude/settings.local.json にテスト専用のルールを置く。
{
"permissions": {
"deny": [
"PowerShell(*perm probe*probe.cmd*)",
"PowerShell(*perm_probe*probe.cmd*)",
"PowerShell(Write-Output positive-control-canary*)"
]
}
}
3行目が陽性対照になる。パスも引用符も絡まない単純なルールで、これが止まらなければ設定ファイル自体が読まれていないと分かる。これを置かずに走らせると、止まらなかったときに「引用符の不具合が残っている」と「ルールがそもそも効いていない」を区別できない。
呼ばれる probe.cmd の中身は @echo probe の1行だけにする。仮に止まらずに実行されても何も壊れない中身にしておけば、テストのために身構えずに済む。空白を含むディレクトリと含まないディレクトリを両方用意するのは、止まらなかったときに原因を引用符へ絞り込むためだ。
期待値の置き方も変えた。リリースノートの当該行はこう書いている。
Fixed PowerShell permission checks mishandling paths containing quote characters on Windows; such paths now prompt for approval
修正後の公称挙動は「承認プロンプトが出る」であって、「deny で止まる」ではない。ここは分けて記録しないと意味が取れなくなる。deny ルールに一致して止まったのか、ルールに一致せずプロンプトへ倒れて止まったのかで、読者に当てはまる範囲が変わるからだ。後者は deny を書いていない人にも起きる。
| # | Claude Code に実行させる形 | 記録する結果 | 結果 |
|---|---|---|---|
| 0 | Write-Output positive-control-canary(陽性対照) |
deny 一致で即停止するか | 未実測 |
| 1 | & "D:\perm probe\probe.cmd" |
deny 一致 / 承認プロンプト / 素通り | 未実測 |
| 2 | & 'D:\perm probe\probe.cmd' |
deny 一致 / 承認プロンプト / 素通り | 未実測 |
| 3 | D:\perm_probe\probe.cmd(呼び出し演算子なし) |
deny 一致 / 承認プロンプト / 素通り | 未実測 |
| 4 | & "D:\perm_probe\probe.cmd" |
deny 一致 / 承認プロンプト / 素通り | 未実測 |
| 5 | cd "D:\perm probe" のあとに相対パスで呼ぶ |
deny 一致 / 承認プロンプト / 素通り | 未実測 |
1 と 2 は引用符の種類だけが違う。3 は呼び出し演算子を外した対照で、& の有無が効いているかを見る。5 を足したのは、パスの書き方が変わったときにルールが追随するかを見たいからだ。
前後比較については、先に断っておくことがある。筆者の環境はすでに 2.1.226 で、v2.1.221 の修正は入ったあとだ。だからこのテストで分かるのは修正後の挙動までで、「以前は止まらなかった」は測れない。旧版を用意しない限り、記事に書く結論も後者を含めない。
回す時間帯にも条件がある。筆者は月曜に記事生成の自動ジョブを無人で走らせていて、動いているあいだはリポジトリを手で触らない。走行中に書き込むと、書込範囲ゲートが予定外の変更として検知し、処理を丸ごと止めてしまう。権限設定をいじる検証は、ジョブが終わってから回す。
起動時間は、公式が謳っていない効能を測ることになる
kernel32 の変更は、起動処理そのものを PowerShell でやっていたという話ではない。プロセス生成時刻を読むために powershell.exe を起動していたのを、ネイティブ呼び出しへ替えたというもの。そして公式が挙げている効能は速度ではなく、powershell.exe をゲートするエンドポイントセキュリティ製品がプロンプトを出さなくなることになる。
起動が軽くなるかどうかは筆者の仮説で、リリースノートは主張していない。測るのはその仮説の確認のためで、差が出なくてもリリースノートが外れたことにはならない。
先に見るべきはこちらだ。powershell.exe をゲートする常駐製品が動いているかどうか。動いていない環境では、公式が挙げる効能は最初から出ない。
時間を測るなら Measure-Command で足りる。
$ms = 1..21 | ForEach-Object { (Measure-Command { claude --version }).TotalMilliseconds }
$ms = $ms | Select-Object -Skip 1 # 1回目は捨てる(ディスクキャッシュが冷えている)
$s = $ms | Sort-Object
"median {0:N1} ms / min {1:N1} / max {2:N1}" -f (($s[9] + $s[10]) / 2), $s[0], $s[-1]
20回まわして中央値と最小・最大を出す。平均を1つだけ出すのはやめる。起動時間はばらつきが大きく、平均は外れ値に引っ張られてしまう。
条件のほうが本番だ。ウイルス対策のリアルタイム検査は実行ファイルの初回スキャンで数百ミリ秒を持っていく。電源プランが省電力だとクロックが上がりきらない。裏でインデクサやバックアップが走っていれば、そちらの I/O に引きずられる。数字を出すなら、条件を並べて書かないと読者が手元の環境と比べられない。筆者の計測条件は次のとおり。
CPU Intel Core i7-14700K
RAM 64GB 実装
OS Windows 11 Pro build 26200
ストレージ システムドライブは SSD
インストール Windows ネイティブバイナリ(C:\Users\username\.local\bin\claude.exe)
電源プラン Razer Cortex Power Plan(省電力ではない)
常駐 エンドポイントセキュリティの有無と、powershell.exe をゲートするかを記録する
| 計測項目 | 取り方 | v2.1.221 より前の版 | 現行版 |
|---|---|---|---|
EDR が powershell.exe をゲートするか |
常駐製品の設定とプロンプト履歴を見る | 未実測 | 未実測 |
| 起動時間の中央値 | 上のスクリプトで21回まわし、1回目を除いた20回から | 未実測 | 未実測 |
| 最小と最大 | 同じ20回から | 未実測 | 未実測 |
| PowerShell プロセスの発生有無 | 起動中に別窓で Get-Process powershell,pwsh を観測 |
未実測 | 未実測 |
いちばん下の行を入れたのは、時間の差より先に「PowerShell が立たなくなったか」を見るほうが確実だからだ。時間はノイズに埋もれることがある。プロセスが立つか立たないかは 0 か 1 で出る。
左列は埋まらない可能性が高い。v2.1.221 より前の実体を用意していないからで、埋まらなければ現行の数字だけを載せて改善幅は書かない。体感で「速くなった」と書くのもやめる。体感は測定条件のばらつきに簡単に負ける。
zsh のバイパスは筆者の環境では確かめられない
同じ版で、zsh の [[ ]] に書いた正規表現条件の中へ隠したコマンドが Bash の権限チェックを素通りできた問題も塞がれた。条件式として読まれる場所に、実行されるものを紛れ込ませる形になる。権限チェックがコマンド文字列の解析に依存している以上、シェルの文法ごとに抜け道が残りうるという話でもある。
これを再現できる環境が手元に無い。筆者は Windows 11 と PowerShell が主で、zsh を日常のシェルにしていない。WSL を入れれば zsh 自体は動く。そこで再現した結果は「WSL 上の zsh でこうなった」までで、macOS や Linux を使っている読者の環境と同じだとは言えない。確かめていないものを確かめた顔で書くと、あとで本当に測ったときに、この記事が邪魔になる。
この項目は未検証のまま置く。zsh を常用している人は、リリースノートの該当行を直接読んで、実際の deny ルールで試すほうが早い。
上げたあとに1回だけやること
v2.1.221 を入れるかどうかで迷う要素は無い。権限チェックの不具合修正が入った版を見送る理由がない。迷うのは上げたあとだ。筆者の環境はすでに修正後の版なので、挙動の確認をこの順で回すことにした。
- deny に書いてあるルールのうち、パスを含むものを1つ選ぶ。ルール名が
PowerShell(...)になっているかを先に確かめる - 引用符つきと引用符なしの両方で呼ばせて、deny 一致・承認プロンプト・素通りのどれになるかを記録する
- Focus view は VS Code 拡張を開いたときだけオンにして、ターミナルの statusline と併存するのかをまず見る
- 起動時間は、
powershell.exeをゲートする常駐製品があるかを先に確かめてから測る - zsh の項目は読むだけにする。手元に検証環境が無い
1つ目と2つ目は5分ほどで終わる見込みになる。deny を書いたときに一度だけ動作を確かめて、それきり見ていない人は多いはずだ。筆者もそうだった。権限まわりの修正は v2.1.214、v2.1.216、v2.1.219、v2.1.221 と、この1か月で何度も入っている。ルールは書きっぱなしにできない運用対象だと考えたほうがいい。
読者に勧める最初の一手も同じにしておく。settings.json の deny を開いて、パスを含む行を1つ選ぶ。Windows で PowerShell を使っているなら、その行が Bash(...) のままになっていないかを見る。そのうえで、パスに空白が入っているなら引用符のつけ方を変えて2回呼ばせてみる。止まらない書き方が1つでも見つかったら、その形を deny 側で明示的に塞ぐ。修正が入った版へ上げること自体は、その確認の代わりにはならない。
参考
- Claude Code v2.1.221 リリースノート(GitHub・公式)
- Configure permissions(Claude Code 公式ドキュメント)
- Claude Code v2.1.214 で allow ルールの dir/** が「効かなくなる」
- Claude Code v2.1.216 が塞いだ隔離の抜け穴
- Claude Code 2.1.219 でサブエージェントが深さ3までネスト可能に
本稿には、再現テストと起動時間計測の実測結果をまだ載せていない。引用符入りパスの再現テスト、起動時間の計測、Focus view と自作 statusline の併存確認は、いずれも未実施になる。zsh の [[ ]] バイパスは Windows 環境で再現できないため未検証。ルールの書式(PowerShell(...)・ワイルドカードの位置・:* の扱い)と、修正後の公称挙動が承認プロンプトである点は、公式ドキュメントとリリースノートの原文で確かめてある。