0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude CodeのPreToolUseフック活用ガイド——AIの操作を「実行される前に」検査する

0
Last updated at Posted at 2026-08-04

Claude Code は本当によく働いてくれます。ファイルを直し、コマンドを叩き、テストまで回してくれる。頼もしい相棒です。

ただ、一緒に働いているとこう思う瞬間があります。

「その操作、実行する前に一回見せてほしかった」

人間なら一瞬手が止まる操作——rm -rfgit push --force、大事な記録ファイルへの直接編集——を、AIは良かれと思って、ためらいなく実行します。「このファイルは触らないでね」とお願いしても、会話が長くなると忘れます。悪気はないんです。ただ、口約束はAI相手には長持ちしません。

そこで使えるのが Claude Code の PreToolUse フックです。フックというのは、決まったタイミングに自作の処理を差し込める仕組みのこと。名前は Pre(〜の前)+ Tool Use(ツールの使用) で、Claude Code の AI はすべての操作を「ツール」経由で実行するため、「ツールを使う前」=操作が実行される直前を指します。ここに自作スクリプトを割り込ませて、「通す・止める・人間に確認を回す」を機械的に判定できるわけです。

この記事では、私が実際に運用しているフックを元に、

  1. 危ないコマンドを実行前に止める
  2. 「直接編集禁止」のファイルを守る
  3. 状況によってガードのON/OFFを切り替える

という3つの実例と、実運用して分かった設計のコツをまとめます。明日、あなたの Claude Code に1本目のガードを仕込めるのがゴールです。

PreToolUseフックの仕組み(30秒で)

Claude Code は、ファイル編集もコマンド実行も、すべて「ツール」と呼ばれる部品を通して行います(Bash・Write・Edit・Read など)。

PreToolUse フックは、そのツールが実行される直前に発火します。

Claude「このコマンドを実行しよう」
   ↓
PreToolUse フック起動(Claude Codeが何をしようとしているか伝わる)
   ↓
自作スクリプトが判定
   ├─ OK → そのまま実行される
   └─ NG → 実行されず、拒否理由が Claude に返る ← ★ここがポイント

ポイントは★の行です。拒否理由は Claude 本人に届きます。 Claude はそれを読んで、別のやり方を考え直します。つまり単なる通行止めではなく、「この先は工事中です。迂回路はこちら」という看板として機能します。

最小構成で動かす

設定は settings.json(プロジェクトなら .claude/settings.json、全プロジェクト共通なら ~/.claude/settings.json)に書きます。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/bash_guard.sh",
            "timeout": 10
          }
        ]
      }
    ]
  }
}
  • matcher:どのツールで発火させるか。ツール名の完全一致で、"Edit|Write" のように複数指定もできます
  • command:起動するスクリプト(あなたが書く判定係です)
  • timeout:秒指定。⚠ 省略すると既定は600秒(後述します)

フックが発火すると、Claude Code は「これから実行しようとしている操作の中身」を JSON にまとめて、あなたのスクリプトの標準入力へ送り込んできます。差出人が Claude Code、受取人があなたのスクリプト、という向きです。届く JSON はこんな形です(抜粋)。

{
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "rm -rf ./build"
  },
  "cwd": "/Users/you/project",
  "session_id": "abc123"
}

tool_input の中身はツールごとに変わります(Bash なら command、Write なら file_pathcontent、といった具合です)。

JSON を受け取ったスクリプトの仕事は、中身を検査して「実行してよいか」の判定を Claude Code へ返すことです。「実行させない」と返せば、Claude Code はそのツール実行(コマンド実行やファイル編集)を取りやめます。これがこの記事で言う「止める」です。

その判定の返し方が2通りあります。

方式 やり方 向いている場面
手軽 exit 2 で終了し、理由を stderr へ とにかく止めたい
丁寧 exit 0 で終了し、判定を JSON で stdout へ 理由や代替手段まで伝えたい

順に実例で見ていきます。

事例1:危ないコマンドを実行前に止める(exit 2方式)

まずは一番シンプルな形から。rm -rfgit push --force を含むコマンドを拒否します。

.claude/hooks/bash_guard.sh

#!/bin/bash
payload=$(cat)
cmd=$(echo "$payload" | jq -r '.tool_input.command // ""')

case "$cmd" in
  *"rm -rf"*|*"git push --force"*|*"git push -f"*)
    echo "ブロックしました: $cmd" >&2
    echo "破壊的な操作は禁止です。対象を限定した安全な方法を提案してください。" >&2
    exit 2
    ;;
esac
exit 0

exit 2 で終了すると、そのツール実行はブロックされ、stderr に書いた文がそのまま Claude へのフィードバックになります。 Claude 側から見ると「実行しようとしたら断られて、理由が返ってきた」状態です。すると「では rm -rf をやめて、対象を確認しながら1つずつ削除します」のように出直してきます。

たった十数行ですが、「事故が起きませんように」という祈りが「起きない仕組み」に変わる瞬間です。

事例2:「直接編集禁止」のファイルを守る(JSON方式)

私の環境には「AIが直接編集してはいけないファイル」があります。作業記録のファイルで、決められたスクリプトを通してだけ追記してほしい。口頭で「直接書かないでね」と頼んでいた頃は、忘れた頃にやらかしてくれました。

こういう「止めたうえで、正しい道も教えたい」場面では JSON 方式が向いています。事例1の bash_guard.sh とは別の独立したフックとして作ります。settings.json の PreToolUse の配列に、もう1エントリ追加してください(フックは何本でも並べられます)。

{
  "matcher": "Write|Edit",
  "hooks": [
    {
      "type": "command",
      "command": "python3 $CLAUDE_PROJECT_DIR/.claude/hooks/write_guard.py",
      "timeout": 10
    }
  ]
}

判定スクリプト本体の .claude/hooks/write_guard.py

#!/usr/bin/env python3
import json
import sys

PROTECTED = ("logs/decision_log.md",)

data = json.load(sys.stdin)
path = data.get("tool_input", {}).get("file_path", "")

if path.endswith(PROTECTED):
    print(json.dumps({
        "hookSpecificOutput": {
            "hookEventName": "PreToolUse",
            "permissionDecision": "deny",
            "permissionDecisionReason": "decision_log.md への直接編集は禁止です",
            "additionalContext": (
                "このファイルへの追記は scripts/add_log.py を使ってください。"
                "例: python3 scripts/add_log.py '追記したい内容'"
            ),
        }
    }, ensure_ascii=False))
sys.exit(0)

permissionDecision に指定できる主な値は3つです。

意味
"deny" 実行させない。理由が Claude に返る
"allow" 許可の確認プロンプトを省略して通す
"ask" 通常の確認プロンプトを出して、人間に判断を回す

ここで実運用上いちばん効くのが additionalContext です。この欄に書いた文は Claude の文脈に確実に入ります。 「止める」だけだと Claude は途方に暮れるか、別の抜け道を探し始めますが、「代わりにこのスクリプトを使って」と書いておくと、素直にそちらへ回ってくれます。通行止めの看板には、迂回路の地図を添える。 これだけで、拒否されたあとのAIの動きが見違えます。

事例3:状況によってガードのON/OFFを切り替える

3つ目は少し応用です。私は外出先から iPhone で Claude Code を操作することがあるのですが、小さい画面では誤操作が怖い。外出先モードのときだけ、特定の操作を止めたり確認に回したりしたいわけです。

フック自体は常設のまま、フラグファイルの有無で挙動を切り替えます。これも事例1・2とは別の独立したフックです。settings.json には、守りたいツールを matcher に並べたエントリ(例:"Bash|Write|Edit")をもう1つ足して、次の .claude/hooks/remote_filter.sh を登録します。

#!/bin/sh
payload=$(cat)

# フラグが無ければ通常モード。何もせず即通す
[ -e "$HOME/.claude/remote_mode_on" ] || exit 0

# 外出先モード中だけ、重い判定へ回す
printf '%s' "$payload" | python3 "$HOME/.claude/hooks/remote_guard.py"

モードの切り替えはコマンド1つです。settings.json を書き換える必要はありません。

touch ~/.claude/remote_mode_on   # 外出先モード ON
rm ~/.claude/remote_mode_on      # OFF

「フックは常に同じ判定しかできない」と思い込みがちですが、環境の状態を見て判定を変える設計にすると、適用範囲がぐっと広がります。"deny" ではなく "ask"(人間に確認を回す)と組み合わせれば、「外出先では確認多め、家では通常運転」のような運用もできます。

実運用で効いた設計のコツ4つ

3つの実例に共通して、実際に回してみて分かったことがあります。

1. 前段はシェル組み込みだけで即抜けする

matcher で絞れるのはツール名までです。つまり "Write|Edit" のフックはすべてのファイル編集で発火します。対象外の編集のたびに Python や jq を起動していると、確実に体感が重くなります(私は一度、この重さが理由で同型の仕組みを廃止したことがあります)。

対策は、前段をシェルスクリプトにして、外部コマンドを1つも呼ばずに対象かどうかを絞ることです。

payload=$(cat)

case "$payload" in
  *decision_log.md*) ;;   # 対象のときだけ下へ進む
  *) exit 0 ;;            # 対象外は即通過(外部コマンド起動ゼロ)
esac

printf '%s' "$payload" | python3 /path/to/heavy_check.py

case はシェルの組み込み機能なのでプロセスが1本も立ちません。この形にしてから、対象外の編集は1桁ミリ秒で通過するようになりました。

なお最近のバージョンには if というフィールドがあり、"if": "Edit(*.md)" のように引数レベルの絞り込みを設定側に書くこともできます。新しめの環境ならこちらも検討してください。

2. timeout は必ず明示する

省略時の既定は 600秒です。判定スクリプトが固まると、ファイル編集そのものが10分止まります。私は一律10秒にしています。

3. 迷ったら通す(フェイルオープン)

判定スクリプト自身にバグがあって「対象かどうか分からない」状態になったとき、拒否側に倒すと何も編集できなくなります。判定できないときは通す・ただしログには残す、が安全です。ルールを守らせる仕組みが本業を止めるのが、いちばん悲しい事故です。

4. 「AIに届く文」と「人間に届く文」を書き分ける

JSON 出力には似た欄がいくつもあって、届く先が違います。

届く先
additionalContext Claude の文脈(行動を変えさせたい文はここ)
permissionDecisionReason 拒否理由として画面や記録に出る
systemMessage 人間の画面に出る警告

私は最初ここを取り違えて、丁寧に理由を書いたのにAIが同じ失敗を繰り返す状態を作りました。AIの行動を変えたい文は additionalContext に入れる、と覚えておくと迷いません。

ちなみに、フックは Claude Code が裏で動かすサブエージェント(並行して働く、もう1人の小さなAI)の操作にも同じように効きます。「本体は止めたのに部下が素通り」という抜け道はありませんでした。

まとめ——「お願い」から「仕組み」へ

  • PreToolUse フックを使うと、AIの操作を実行される前に検査できる
  • 手軽に止めるなら exit 2 + stderr、丁寧に導くなら JSON + additionalContext
  • 前段はシェル組み込みで即抜け・timeout は明示・迷ったら通す、が実運用の三種の神器

AIに「気をつけてね」と言い続ける生活から、「気をつけなくても事故らない」環境づくりへ。1本目のガードは、この記事の事例1をコピーすれば10分で動きます。ぜひお手元の Claude Code に仕込んでみてください。

参考リンク

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?