Claude Code の hooks で自動チェックを仕込む

概要

Claude Code には、エージェントの動作の節目でユーザー定義のコマンドを実行する hooks という仕組みがあります。「ファイルを編集したら自動で整形する」「危険なコマンドを実行前にブロックする」といった処理を、プロンプトでのお願いではなく決定論的なフックとして差し込めます。

プロンプトで「編集後は必ず prettier をかけて」と頼んでも、守られたり守られなかったりします。hooks は AI の判断に依存せず、指定イベントで必ずコマンドが走るのが本質的な違いです。本記事では、次の3つをハンズオンで作ります。

  • PostToolUse で編集後に自動フォーマット
  • PreToolUse で危険コマンドをブロック
  • Stop で作業完了時にチェックを走らせる

環境

  • Claude Code(CLI)
  • macOS / Linux(Windows は WSL 推奨)
  • jq(フックに渡る JSON を扱うため)

hooks の設定は .claude/settings.json に書きます。配置場所でスコープが変わります。

ファイル スコープ
~/.claude/settings.json ユーザー全体
<repo>/.claude/settings.json プロジェクト(チーム共有・コミット対象)
<repo>/.claude/settings.local.json プロジェクト(個人・gitignore 対象)

hooks の基本構造

イベントごとに、matcher(対象ツール名の正規表現)と実行コマンドの組を登録します。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "echo 'ファイルが変更されました'" }
        ]
      }
    ]
  }
}

主なイベント:

イベント 発火タイミング
PreToolUse ツール実行の直前(ブロック可能)
PostToolUse ツール実行の直後
UserPromptSubmit ユーザーがプロンプトを送信した時
Stop エージェントが応答を終える時
SessionStart セッション開始時

matcher は Bash / Edit / Write などのツール名にマッチし、Edit|Write のように正規表現で複数指定できます。

フックに渡ってくる入力

フックのコマンドには、標準入力(stdin)経由で JSON が渡されます。PreToolUse / PostToolUse ではおおむね次の形です。

{
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "rm -rf build/"
  }
}

jq で必要なフィールドを取り出して処理します。

ハンズオン1: 編集後に自動フォーマット(PostToolUse)

Edit / Write が走ったら、対象ファイルに整形をかけます。tool_input.file_path に編集対象のパスが入っています。スクリプトに切り出すと読みやすいです。

.claude/hooks/format.sh:

#!/usr/bin/env bash
set -euo pipefail

file_path=$(jq -r '.tool_input.file_path // empty')
[ -z "$file_path" ] && exit 0
[ -f "$file_path" ] || exit 0

case "$file_path" in
  *.ts|*.tsx|*.js|*.jsx) npx prettier --write "$file_path" ;;
  *.py)                  ruff format "$file_path" ;;
  *.go)                  gofmt -w "$file_path" ;;
esac
exit 0
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "bash .claude/hooks/format.sh" }
        ]
      }
    ]
  }
}

これで、AI が編集するたびに整形が走り、差分がフォーマッタ準拠に揃います。

ハンズオン2: 危険コマンドをブロック(PreToolUse)

PreToolUse は実行前に発火し、終了コードでツール実行を止められます。

  • 終了コード 0: 許可
  • 終了コード 2: ブロック(stderr の内容が Claude にフィードバックされる)
  • それ以外: 非ブロックのエラー扱い

.claude/hooks/guard.sh:

#!/usr/bin/env bash
set -euo pipefail

command=$(jq -r '.tool_input.command // empty')

if echo "$command" | grep -qiE 'rm[[:space:]]+-rf[[:space:]]+/|git[[:space:]]+push[[:space:]]+--force'; then
  echo "危険なコマンドを検出したためブロックしました: $command" >&2
  exit 2
fi
exit 0
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "bash .claude/hooks/guard.sh" }
        ]
      }
    ]
  }
}

stderr に書いたメッセージは Claude に返るため、単なる拒否ではなく理由つきのガードレールになります。より細かく制御するなら stdout に JSON を出す方法もあります。

echo '{"decision": "block", "reason": "破壊的コマンドは手動確認が必要です"}'
exit 0

ハンズオン3: 完了時にチェックを走らせる(Stop)

エージェントが応答を終える Stop イベントで、軽いチェックを回します。

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          { "type": "command", "command": "npm run -s lint || true" }
        ]
      }
    ]
  }
}

Stop はツールに紐づかないため matcher は不要です。重い処理は体験を損なうので、lint や tsc --noEmit など軽いものから始めます。

確認方法

  1. .claude/settings.json に hooks を書く。
  2. Claude Code を起動し直す(設定の再読込のため)。
  3. 動作確認: 編集→整形、rm -rf を含むコマンド→ブロック、応答終了→lint、をそれぞれ確認。
  4. フック単体はサンプル JSON を食わせて検証できます。
echo '{"tool_input":{"command":"rm -rf /"}}' | bash .claude/hooks/guard.sh
echo "exit=$?"   # => exit=2 になれば成功

注意点

  • hooks はあなたの権限でシェルを実行する。信頼できないリポジトリの設定を鵜呑みにしない。
  • コマンドがハングするとセッションが待たされる。|| true やタイムアウトで早く返す。
  • PostToolUse のフォーマッタがファイルを書き換える点に注意。フック内からエージェントのツールを呼ばない。
  • パスはリポジトリルート起点で書く。
  • イベント名や入力 JSON のフィールドはバージョンで変わりうるため、導入時に公式ドキュメントで最新を確認する。

まとめ

hooks は、AI の「お願いベース」の約束を決定論的な自動処理に変える仕組みです。PostToolUse で整形、PreToolUse でガードレール、Stop で完了時チェック——この3点セットが実用的です。制御は終了コード(2 でブロック)か stdout の JSON。実体はシェル実行なので、セキュリティレビューと軽量化を忘れずに。守ってほしいルールほど、お願いではなく仕組みに落とす価値があります。

\ 最新情報をチェック /

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください。