Claude Code——AIがコマンドを実行しながら開発を手伝うツール(以下CC)——に、危ない操作を止めるフックを入れている人は多い。フックというのは、CCがコマンドを実行する直前に割り込んで、内容を検査して止めたり確認を出したりする仕組みだ。
そのフックに、こういう不安がついて回る。
「
--dangerously-skip-permissionsを付けて走らせている時、
このフックはちゃんと止めてくれるのか?」
権限の確認を全部飛ばすモードなのだから、フックの出す確認も一緒に飛ばされるのではないか。もしそうなら、無人で回している夜間こそ守ってほしいのに、そこでだけ守りが消えることになる。
私は非エンジニアで、実装も調査もCCにやらせながら24時間走らせている。当然この不安の当事者だ。しかも私は、CCの安全設定をまとめたものを無料で配っている側でもある。
その配っているものの中に、「このモードでは確認は出ない」と書いてあった。
測ったら、出た。
この記事では、自分の環境で同じことを確かめる手順を渡す。3分で終わる。あわせて、私がその3分の間に2回踏んだ罠も書く。罠のほうが本題かもしれない。片方は、それらしい表を出したまま結論を間違えるやつだった。
測る手順
考え方は単純だ。「確認が出るはずの状況」を人工的に作って、モードだけを変える。
1. 確認を出すだけのフックを作る
中身のある検査は要らない。呼ばれたら必ず「確認して」と返すだけのものでいい。目印になる文字列を理由に入れておくのがコツだ。
#!/bin/sh
cat > /dev/null
printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":"MARK-12345: これを許可しますか"}}\n'
exit 0
permissionDecision に ask を返すと、CCは人に「やっていいか」を尋ねる。この ask が緩いモードで無視されるのかどうかが、まさに知りたいことだ。
2. そのフックを呼ぶ設定を書く
{
"hooks": {
"PreToolUse": [
{ "matcher": "Bash",
"hooks": [{ "type": "command", "command": "/絶対パス/ask-hook.sh" }] }
]
},
"permissions": { "allow": [], "deny": [] }
}
3. 何か1つ実行させて、画面を見る
claude --settings ./s.json --dangerously-skip-permissions
起動したら「node probe.mjs を実行して」のように、Bashを1回使う指示を出すだけ。確認が出るか、出ないまま実行されるかを見る。
私の環境(2.1.228 / WSL2)では、確認が出た。フックに書いた MARK-12345 の文字までそのまま表示され、コマンドは答えを待ったまま実行されなかった。--permission-mode auto でも同じだった。
罠その1:対照を取らないと、この結果は何も言っていない
ここで止めてはいけない。
確認が出た。めでたい。——本当に「フックが尊重された」のか?
もう1つの可能性がある。そもそも --dangerously-skip-permissions が効いていなかっただけかもしれない。旗の綴りが違う、設定ファイルに上書きされている、そのビルドでは別の名前になっている。理由はいくらでもある。その場合、出た確認は「普通のモードの普通の確認」であって、フックの手柄ではない。
だから同じ旗のまま、フックだけ外してもう1回走らせる。
| フックを入れた | フックを外した(対照) | |
|---|---|---|
| 既定のモード | 確認が出た | 確認が出た |
--dangerously-skip-permissions |
確認が出た | 確認なしで実行された |
--permission-mode auto |
確認が出た | 確認なしで実行された |
右の列があって初めて、左の列が読める。フックを外したら確認なしで走った=緩いモードは本当に効いていた。その状態で確認が出たのだから、出したのはフックだ。
対照は「念のため」ではない。対照が無い実験は、結論の向きが決まらない。
ちなみに非対話(claude -p、答える人がいない状態)では、通るのではなく拒否された。「聞く相手がいないので通す」ではなく「聞けないので止める」側に倒れている。
罠その2:4条件とも同じ結果が出たら、それは答えではない
最初に走らせた時、私は4つの条件を一度に測って、こういう表を得た。
条件 確認 実行
既定 + フック 出ない 実行されない
権限を飛ばす + フック 出ない 実行されない
既定 + フック無し 出ない 実行されない
権限を飛ばす + フック無し 出ない 実行されない
綺麗に揃っている。「どのモードでも確認は出ない」という結論が書けそうな見た目だ。
全部嘘だった。
画面の生の記録を開いたら、4回とも同じ画面で止まっていた。ログイン方法を選ぶ画面だ。実験用に隔離した環境に認証を持たせ忘れていて、CCは一度も起動していなかった。何も測っていない4行が、表の形をしていただけだった。
ここから2つ持ち帰った。
(1)「全条件が同じ」は、発見ではなく故障の第一容疑者。条件を変えたのに結果が動かないなら、まず疑うのは世界ではなく自分の測り方だ。
(2)実験の道具には、成立していない時に大声で落ちる仕掛けを入れる。私はこの後、画面に「ログイン方法」や「Not logged in」が出ていたら表を作らずに止まるようにした。黙って揃った表を出す道具は、いちばん危ない道具だ。
おまけの罠:画面から文字を拾う時、空白は消える
2回目の測定でも一度間違えた。「確認が出た」を判定するのに Do you want to proceed を探していたのだが、画面から取り込んだ文字列では Doyouwanttoproceed? になっていた。
CCの画面は文字を「この位置にこの字」という形で描くので、素直に読むと空白が落ちる。実行された印の ran probe も ranprobe になっていた。おかげで「実行されていない」と判定され、対照の1つが逆に出ていた。
比較する前に空白を全部落とす。それだけで直った。
自分の配っていたものが間違っていた
冒頭の話に戻る。
私が配っているフックのうち3本に、「緩いモードでは ask は黙って自動承認されるので、この確認は出ない」という注意書きが入っていた。公開している解説のページにも、「auto モードでは ask を使うな、deny に置き換えろ」と現在形で書いてあった。
出所は実在の報告だ。片方は v2.1.114 で確認されたと明記されている。当時は本当だったのだろう。
だが 2.1.228 では再現しない。上の表がそれだ。
害の向きが悪い。読者は効いている安全機構を「効かない」と教わり、しかも「その場で許可する選択肢のある ask」を捨てて「永久に拒否する deny」へ置き換えろと勧められていた。効くビルドでは、失うだけで得るものがない交換だ。
版に紐づけて書き直した。何を書き直したかより、何を書けなかったかのほうが大事だと思う。
- v2.1.114 と 2.1.228 のどのビルドで変わったのかは特定できていない
- あなたの版がどちらかは分からない。
claude --versionで確かめて、上の3分の手順を自分で回してほしい -
askが尊重されるとしても、答える人がいない完全な無人運転では意味が薄い。そこはdenyや終了コード2で止めるのが筋で、この助言自体は前から正しかった。間違っていたのは理由のほうだった
教訓
安全のための設定は、入れた日の挙動のまま止まっていると思い込みやすい。実際にはツール側が動き続けていて、去年正しかった注意書きが今年は逆になる。
しかも困ったことに、この種の情報は伝聞で広がる。私も報告を読んで、自分の配布物にそのまま書いた。自分の環境で1回も走らせずに。
だから、こう言い直したい。
入れたフックが本当に止めるかは、入れた人が測るしかない。3分で済む。対照を2方向で取れ。全条件が同じ表が出たら、それは答えではなく壊れた印だ。
私が使っている安全フック一式は無料で公開している。この記事で直した3本もそこに入っている。→ cc-safe-setup
この記事で扱ったような「去年の正解が今年は逆になる」は、毎月あたらしい型で出てくる。その月に実際に起きた事故と仕様の変更を、実機で確かめてから月に1本にまとめている(Claude Code 安全運用便・¥500/月・最初の1か月は無料)。無料で足りる人は、上の hook をそのまま使ってほしい。