AIエージェント入門

Claude Code Hooks|PreToolUse実践ガイド

Claude Code Hooks|PreToolUse実践ガイド

この記事の結論

Hooksの実践ガイド。PreToolUse・PostToolUseの設定とdefer機能でヘッドレス制御。

まず試したい「5分即効」セットアップ3選

Hooksの理論より先に、動くコードから入ろう。以下の3つは今日から使えるパターンだ。

即効テクニック1:全ファイル変更を監査ログに記録する

Claudeがどのファイルをいつ変更したかを audit.log に記録するPostToolUseフック。チームでClaude Codeを使う時の透明性確保に効く。


// ~/.claude/settings.json に追記
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "echo "$(date -Iseconds) $CLAUDE_TOOL_NAME $CLAUDE_TOOL_INPUT_FILE_PATH" >> ~/audit.log"
          }
        ]
      }
    ]
  }
}

動作環境: Claude Code v1.0+, macOS/Linux
ポイント: $CLAUDE_TOOL_NAME$CLAUDE_TOOL_INPUT_FILE_PATH は環境変数としてフックスクリプトに渡される

即効テクニック2:Pythonファイル編集後に自動でlintを実行する

Claudeがコードを書くたびにruffが走り、問題があればClaudeに伝わる。コードレビューサイクルが自然に短縮される。


{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit",
        "hooks": [
          {
            "type": "command",
            "command": "python3 -c "import json,sys; d=json.load(sys.stdin); f=d.get('tool_input',{}).get('file_path',''); print(f)" | xargs -I{} sh -c 'echo {} | grep -q \.py && ruff check {} 2>&1 || true'"
          }
        ]
      }
    ]
  }
}

ポイント: フックはstdinにJSON形式のツール入力を受け取る。tool_input.file_path から編集対象ファイルのパスを取得できる

即効テクニック3:危険なrmコマンドをブロックする

PreToolUseフックでBashの危険なコマンドを検出してブロックする。誤った削除操作を防ぐ安全機構として有効だ。


#!/bin/bash
# ~/.claude/hooks/block_dangerous_bash.sh

INPUT=$(cat /dev/stdin)
CMD=$(echo "$INPUT" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('tool_input',{}).get('command',''))")

# rm -rf / や rm -rf ~/ などを検出してブロック
if echo "$CMD" | grep -qE 'rms+-rfs+(/|~/|/home/|/usr/|/etc/)'; then
    echo '{"decision":"block","reason":"危険なrmコマンドを検出しました。削除先のパスを確認してください。"}'
    exit 0
fi

# DROP TABLE などのSQL破壊的操作をブロック
if echo "$CMD" | grep -qiE '(DROPs+TABLE|TRUNCATEs+TABLE|DELETEs+FROM.*WHEREs+1=1)'; then
    echo '{"decision":"block","reason":"破壊的なSQLコマンドを検出しました。意図した操作か確認してください。"}'
    exit 0
fi

exit 0  # それ以外は許可

// settings.jsonへの登録
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/block_dangerous_bash.sh"
          }
        ]
      }
    ]
  }
}

動作環境: Claude Code v1.0+, Bash 3.2+
注意: 本番環境で使用する前に、必ずテスト環境で動作確認してください。


HooksシステムはClaude Codeの「3つの制御レイヤー」で動く

Hooksを理解するには、Claude Codeがツールを実行する流れを把握しておくと良い。

フェーズ フックの種類 できること できないこと
ツール実行前 PreToolUse 許可・拒否・defer・入力変更 実行後の結果を変える
ツール実行後 PostToolUse フィードバック・ログ・MCP出力変更 実行を取り消す
セッション終了時 Stop テスト実行・後処理・強制継続 ツール入力を変更する
セッション開始時 SessionStart 初期化・環境確認 重い処理(毎回走る)
ユーザー入力時 UserPromptSubmit プロンプトのバリデーション・変換 ツールの制御

重要な設計原則として、「止めたいならPreToolUse、記録・検証はPostToolUse」と覚えておくとよい。PostToolUseでブロック(decision: “block”)を返すとClaudeにフィードバックが伝わるが、ツールは既に実行済みだ。


defer機能 — ヘッドレスモードでの承認フロー実装

2026年に追加された defer は、CI/CDや自社UIからClaude Codeを制御したい場合に特に重要な機能だ。

通常、Claude Codeはインタラクティブに動作する。だが claude -p "..." のヘッドレスモードでは、ユーザーへの確認(AskUserQuestionツール)が詰まってしまう。そこで defer を使う。

deferの動作フロー


# 1. ヘッドレスモードで起動(--output-format stream-json は構造化出力)
claude -p "production環境にデプロイして" --output-format stream-json > /tmp/claude_output.json

# 2. deferが発生すると exit code 0 で停止し、deferred_tool_use が出力に含まれる
# 出力例:
# {"type":"result","subtype":"tool_deferred","deferred_tool_use":{"id":"toolu_01abc","name":"AskUserQuestion","input":{"questions":[{"type":"text","text":"本当にproductionにデプロイしますか?"}]}}}

# 3. deferred_tool_use を確認してユーザーに承認を求める(独自UI)
# ... 独自の承認ロジック ...

# 4. 承認が得られたらセッションを再開(--resume でセッションIDを指定)
SESSION_ID=$(cat /tmp/claude_output.json | python3 -c "import json,sys; [print(d.get('session_id','')) for line in sys.stdin for d in [json.loads(line)] if d.get('session_id')]" | tail -1)
claude -p "承認済み: デプロイを続けて" --resume $SESSION_ID --output-format stream-json

制約(重要): deferはClaude Codeが1ターンに1つのツールコールのみ行う場合にのみ機能する。複数ツールを並列実行するターンでは使えない。

Agent SDKでのdefer実装


# 動作環境: Python 3.10+, claude-agent-sdk>=0.2.0
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, HookMatcher

# カスタムUIで承認を収集するフック(defer版)
async def defer_for_human_approval(input_data, tool_use_id, context):
    tool_name = input_data.get("tool_name", "")
    if tool_name == "Bash":
        cmd = input_data.get("tool_input", {}).get("command", "")
        if "deploy" in cmd or "production" in cmd:
            # deferを返すとSDKが一時停止し、呼び出し元が判断できる
            return {
                "hookSpecificOutput": {
                    "hookEventName": "PreToolUse",
                    "permissionDecision": "defer",
                    "permissionDecisionReason": f"本番環境操作のため人間の承認が必要: {cmd}"
                }
            }
    return {}

async def main():
    async for message in query(
        prompt="APIサーバーをproduction環境にデプロイして",
        options=ClaudeAgentOptions(
            allowed_tools=["Bash", "Read"],
            hooks={
                "PreToolUse": [
                    HookMatcher(matcher="Bash", hooks=[defer_for_human_approval])
                ]
            },
        ),
    ):
        # stop_reason が tool_deferred の場合に独自UIで承認処理
        if hasattr(message, "stop_reason") and message.stop_reason == "tool_deferred":
            print(f"承認待ち: {message.deferred_tool_use}")
            # ここで独自UIやSlack通知などで承認を収集する

# 注意: 本番環境で使用する前に、必ずテスト環境で動作確認してください。
asyncio.run(main())

CI/CD連携の実践例 — pre-commitスタイルの品質ゲート

HooksとヘッドレスモードはCI/CDのパイプラインに組み込むこともできる。Stopフックを使ってテストを強制実行する例を示す。


#!/bin/bash
# ~/.claude/hooks/run_tests_after_edit.sh
# Stopフックで登録する。セッション終了時にテストを実行し、失敗したら継続させる

if git diff --name-only HEAD | grep -q '.py$'; then
    echo "Pythonファイルの変更を検出。テストを実行します..."
    if ! python3 -m pytest tests/ -q 2>&1; then
        echo "テストが失敗しました。修正してください。"
        exit 2  # exit 2 はClaudeに「まだ続けろ」のシグナル
    fi
    echo "テスト全て通過しました。"
fi

exit 0

// settings.jsonへの登録
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/run_tests_after_edit.sh"
          }
        ]
      }
    ]
  }
}

ポイント: Stopフックで exit 2 を返すと、Claudeは「まだやることがある」と判断してセッションを継続する。テストが通るまでClaudeに修正を続けさせる自律的なループが実現できる。


【要注意】よくある失敗パターンと回避策

失敗1: PreToolUseとPostToolUseを逆に使う

❌ PostToolUseで decision: "block" を返してツール実行を止めようとする
⭕ ツール実行を止めたい場合はPreToolUseを使う

なぜ重要か: PostToolUseはツールが完了してから呼ばれる。この時点でブロックしても実行は取り消せない。フィードバックはClaudeに伝わるが、変更は済んでいる。

失敗2: SessionStartフックに重い処理を書く

❌ SessionStartフックでAPIコールや大きなファイルのロードを行う
⭕ SessionStartは軽い初期化のみ。重い処理はトリガーが明確なPreToolUseやPostToolUseに書く

なぜ重要か: SessionStartはClause Codeのセッションが始まるたびに毎回実行される。重い処理を書くとClaude Codeの起動が遅くなる。

失敗3: シェルプロファイルの出力がフックのJSONを壊す

~/.bashrc~/.zshrcecho "ようこそ" などの出力がある状態でコマンドフックを使う
⭕ フックスクリプトは #!/bin/bash -l(ログインシェル)ではなく #!/bin/bash で書き、プロファイルの出力が混入しないようにする

なぜ重要か: Claude Codeはフックのstdoutを全てJSONとしてパースしようとする。シェルの初期化メッセージが混入するとパースエラーになり、フックが正常に動作しない。

失敗4: matcherの正規表現をテストしない

"matcher": "Edit" と書いたつもりが、MCPツールの mcp__filesystem__edit_file とマッチしていると思い込む
⭕ MCPツールは mcp__<server>__<tool> 形式のため、明示的に "matcher": "Edit|mcp__.*__edit.*" のように書く

なぜ重要か: 組み込みツール名(Edit, Write, Bash等)とMCPツール名は形式が違う。片方しかマッチしていないフックは意図通りに動作しない。


参考・出典


MCPサーバーとの組み合わせについてはClaude CodeとMCPのコンテキスト最適化ガイドも参照してください。Claude Agent SDKでHooksをプログラムから制御する方法はClaude Agent SDK入門ガイドにまとめています。セキュリティの観点からエージェントの振る舞いを制御する考え方はAgent Governance Toolkitの活用事例も参考になります。

まとめ:今日から始める3つのアクション

  1. 今日: PostToolUse フックでファイル変更ログを audit.log に記録するだけの1行フックを設定してみる
  2. 今週中: PreToolUse フックで危険なBashコマンドのブロックリストを作り、誤削除防止を実装する
  3. 今月中: Stop フックとCI連携を組み合わせ、テストが通るまでClaudeが自律的に修正するループを本番パイプラインに組み込む

ご質問・ご相談は お問い合わせフォーム からお気軽にどうぞ。


著者: 佐藤傑(さとう・すぐる)
株式会社Uravation代表取締役。早稲田大学法学部在学中に生成AIの可能性に魅了され、X(旧Twitter)で活用法を発信(@SuguruKun_ai、フォロワー10万人超)。100社以上の企業向けAI研修・導入支援を展開。著書累計3万部突破。

Need help moving from reading to rollout?

この記事を読んで導入イメージが固まってきた方へ

Uravationでは、AIエージェントの要件整理、PoC設計、社内導入、研修まで一気通貫で支援しています。

この記事をシェア

X Facebook LINE

※ 本記事の情報は2026年4月時点のものです。サービスの料金・仕様は変更される可能性があります。最新情報は各サービスの公式サイトをご確認ください。

関連記事