「重い処理はサブエージェントに逃がす」「料金の高い操作は一度立ち止まる」。自分で決めたルールを、毎回きっちり守るのは無理でした。人は忘れます。そこでClaude Codeのhooksに運用ルールを持たせて、うっかりとコストの浪費を機械に止めてもらうことにしました。実際にこのブログの自動化プロジェクトで動かしているPreToolUseフックを、設定ごと公開します。
結論:PreToolUseフックは「ブロック」より「注入」と「確認」が効く
hooksというと「危険なコマンドをブロックする」使い方が有名です。ただ、実際に運用してみると、止めるだけが正解ではありませんでした。ツール実行の直前に割り込めるPreToolUseには、返し方が3つあります。通す(allow)、確認を挟む(ask)、止める(deny)。ここに運用ルールの文章を添えて「注入」できるのが、地味にいちばん効きました。

フックの仕組み(stdinのJSON→3つの返し方)
PreToolUseフックは、Claudeがツールの引数を決めた直後、実際に実行される直前に発火します。フックには実行しようとしているツール名や引数がJSONで stdin から渡されます。フックはそれを読んで、標準出力にJSONで判断を返す。この往復だけです。
返すJSONの主なキーはこの3つです。
permissionDecision:"allow"/"ask"/"deny"のどれかpermissionDecisionReason: なぜそう判断したかの一文(画面に出る)additionalContext: Claudeに追加で読ませたい文章(=運用ルールの注入)
大事なのは、判定に必ずしもLLMを使わないことです。type を command にすれば、ただのシェルスクリプトが判定します。確実に効かせたいガードほど、揺らぎのない command 型が向いていました。
実例①:高コストなスキルに運用ルールを注入する(additionalContext)
このプロジェクトでいちばん重いのが、WordPressへ記事を投稿するスキル(publisher)です。実行するとWP-CLIの長いログや画像が本体の文脈に流れ込み、トークンを食います。そこで、このスキルが呼ばれたら「サブエージェントに委譲せよ」という運用ルールを毎回注入するようにしました。実際の settings.json がこれです(コマンド部分は読みやすく整形しています)。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Skill",
"hooks": [
{
"type": "command",
"shell": "bash",
"timeout": 10,
"statusMessage": "コスト規律ガード",
"command": "in=$(cat);
if printf '%s' "$in" | grep -qE '"skill"[[:space:]]*:[[:space:]]*"koma-publisher"'; then
printf '%s' '{"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":"コスト規律: publisher はサブエージェントに委譲し、ログ/画像を本体文脈に持ち込まない。肥大していれば先に /clear。"}}';
fi"
}
]
}
]
}
}
ポイントは matcher を "Skill" にして、スキル呼び出し全体を1つのフックで受けていることです。あとは stdin のJSONを grep して、対象のスキル名なら注入する。止めていないので作業は続きますが、Claudeは注入された一文を読んでから動きます。分岐は次の図のとおりです。

実例②:課金の重い操作にだけ確認を挟む(ask)
同じフックの中で、別のスキルには ask を返しています。価格を調べたいだけなのに、料金表以外の情報まで含む重いスキル(claude-api)を丸ごと開いてしまうことがありました。そこで、このスキルが呼ばれたら一度確認を挟みます。
elif printf '%s' "$in" | grep -qE '"skill"[[:space:]]*:[[:space:]]*"claude-api"'; then
printf '%s' '{"hookSpecificOutput":{"hookEventName":"PreToolUse",
"permissionDecision":"ask",
"permissionDecisionReason":"価格確認だけなら claude-api を開かず _shared/claude-pricing.md を読む。本当に必要なときのみ続行。"}}'
fi
ask にすると、実行前に理由付きで確認が出ます。「本当にこの重いスキルが要る?」と一拍おけるだけで、軽い代替(料金メモを読む)に切り替える判断ができました。denyのように完全に塞がないので、本当に必要なときはそのまま進めます。
なぜ deny 一辺倒にしないのか
denyで強制ブロックすると、Claudeは別の手を探して再試行します。その試行錯誤でやりとりが増え、かえってレイテンシもコストも膨らむことがありました。禁止したい操作が明確なら、もちろんdenyが正解です。ただ「やってほしいが、やり方に注意させたい」ケースでは、止めるより注入や確認のほうが素直でした。
整理すると、こう使い分けています。壊れると困る操作はdeny。コストや流儀に気をつけてほしい操作はallow+注入。判断を人に委ねたい操作はask。3つを持っておくと、運用ルールをそのままフックに落とせます。
設定して分かった注意点
- command型はLLMを使わない:確実性が要るガードは
commandで。prompt型はLLM呼び出しのぶんコストと揺らぎが増えます - タイムアウトを短く:ガードは軽い判定なので
timeoutを10秒などに。毎回のツール実行に挟まるので重いと体感が悪くなります - matcherは広めに受けて中で分岐:
"Skill"のように大きく受け、スクリプト内でスキル名を見て振り分けると、フックが増えすぎません - バージョンを確認:hooksは近年のClaude Codeのバージョンで使えます。
/hooksと打つと、いま効いている設定を読み取り専用で確認できます
この使い方が向かない場合
フックも万能ではありません。合わない場面もあります。
- チームでルールがまだ固まっていない:頻繁に変わるルールをフックにすると、メンテのほうが重くなります。運用が安定してから固めるとよいです
- 複雑な判断が必要:文脈をふまえた高度な判定は
commandのgrepでは書けません。そこは無理にフック化しないほうが安全です - 単発の作業:一度きりの作業なら、フックを書く時間のほうがもったいないかもしれません
逆に、繰り返す作業で毎回同じ注意を守りたいなら、フックは効きます。Claude Codeの運用を仕組み化する話は自作スキルの設計の記事やClaude Codeの便利な使い方の記事にも書きました。ほかの実装は爆速開発・環境構築のカテゴリにまとめています。
まとめ
- PreToolUseフックはツール実行の直前に割り込み、allow / ask / deny を返せる
- 止める(deny)だけでなく、additionalContextで運用ルールを「注入」、askで「確認」を挟める
- 確実性が要るガードはLLMを使わない
command型で書く matcherは広く受けて、スクリプト内でスキル名や引数を見て分岐する- 頻繁に変わるルールや複雑な判断には向かない。安定した運用ルールを固めるのに向く
フックは、自分の注意力に頼らず運用の質を一定に保つ仕組みです。こうした開発・運用の自動化を業務に組み込む設計は、運営元のNEORIVAでも承っています。AIツールの導入で「事故らない運用」を作りたい方は、気軽に相談してください。参考にした公式ドキュメントは Claude Code Hooks リファレンス と settings の解説 です。