Claude Codeのhooksとは?settings.jsonでツール実行に介入する

公開日:
目次

Claude Codeを毎日使っていると、特定のルールを毎回守らせたかったり、ある操作だけは事前に止めたい場面ってありますよね。CLAUDE.mdに書くだけでは忘れられることもあり、忘れて欲しくない動作は別の仕組みで縛りたくなります。自分はブログ運用をClaude Codeに任せているので、日本語固定のルールや危険コマンドの遮断をhooks経由で実現しています。

hooksとは

hooksはClaude Codeの動作の節目(プロンプトを送ったとき、ツール実行の前後、応答が終わったときなど)に自分のシェルスクリプトを差し込める仕組みです。決められた場面で呼ばれ、標準入力で実行情報を受け取り、終了コードや標準出力で挙動を制御できます。

ツール呼び出しを中断したり、入ってきた情報を元に分岐したり、Claudeのコンテキストに文字列を差し込んだり、といった操作が書けます。設定は settings.json に書きます[1]

settings.jsonの基本構造

hooksの定義は3階層のJSONです。最初は深く見えますが、「イベントごと → どの対象に絞るか → 何を実行するか」という流れで考えると追えます。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check.sh"
          }
        ]
      }
    ]
  }
}

イベント名(最外)

最外の hooks の中に、発火する場面を示すイベント名をキーとして並べます。PreToolUse(ツール実行直前)、UserPromptSubmit(プロンプト送信時)、Stop(応答終了時)などが代表的です。

matcher(中間)

matcher には、そのイベントが発火する条件を書きます。PreToolUse のようなツール系イベントなら絞り込み対象はツール名です。

  • 空文字 / "*" / 省略 — 全マッチ
  • Bash のような単一名 — 完全一致
  • Edit|Write| 区切りで複数指定
  • mcp__memory__.* — 正規表現として評価

MCPサーバ経由の道具は mcp__<サーバ名>__<ツール名> の形式なので、正規表現で一気に絞り込めます。

handlers(最内)

最内の hooks には、呼び出す処理を並べます。type: "command" を指定すると外部の実行ファイルを叩けます。${CLAUDE_PROJECT_DIR} を使えば、プロジェクト直下を基準にした位置で指定でき、別の場所から起動してもパスがずれません。

よく使うイベント

公式には20種類以上のイベントがありますが[1:1]、自分の使用範囲では大半が次の数イベントに収まります。

UserPromptSubmit

ユーザーがプロンプトを送信した瞬間に発火します。「日本語で答えてほしい」のような全体ルールを毎回挿入したいときに便利です。標準出力に書いた内容はClaudeのコンテキストに混ぜられ、exit 2 を使えばプロンプト自体を弾けます。自分はCLAUDE.mdだけに頼らず、UserPromptSubmitで固定ルールを毎回流す方式を併用しています。

PreToolUse

ツール実行の直前に発火します。BashEdit などのツール名で絞り、exit 2 でツール呼び出し自体をブロックできます。rm -rf のような危険な指示が出てきたときに止める使い方が代表例です。

PostToolUse

ツール実行が成功した直後に発火します。ファイル編集後に自動でフォーマッタを当てる・テストを走らせるなど、後始末系の処理を仕込むのに使えます。

Stop

Claudeが応答を終えたタイミングに発火します。長時間の作業が終わったら通知を飛ばす、音を鳴らす、といった連絡系で重宝します。

UserPromptSubmitで固定ルールを毎回流す

.claude/settings.json:

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/inject-rule.sh"
          }
        ]
      }
    ]
  }
}

.claude/hooks/inject-rule.sh:

#!/bin/bash
echo "# ルール"
echo "- 日本語で回答すること"

スクリプトには実行権限を付けておきます(chmod +x .claude/hooks/inject-rule.sh)。UserPromptSubmit フックが標準出力に書いた内容は、そのターンの追加コンテキストとしてClaudeに渡ります。実際にこの設定を入れてみると、英語で短く尋ねた場合でも応答が日本語に固定されるようになりました。

PreToolUseで危険コマンドを止める

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh"
          }
        ]
      }
    ]
  }
}

.claude/hooks/block-rm.sh:

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

if echo "$CMD" | grep -qE 'rm[[:space:]]+-rf?'; then
  jq -n '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: "rm -rf は禁止しています"
    }
  }'
  exit 0
fi
exit 0

PreToolUse フックの標準入力には tool_nametool_input などを含むJSONが渡ります。jqtool_input.command を取り出し、危険な指示なら permissionDecision: "deny" を返してブロックします。exit 2 で終了させてもブロックできますが、JSONで理由文を返すとClaudeにもその理由が伝わり、以降の応答がスムーズになります。

設定の置き場所

場所 適用範囲 チーム共有
~/.claude/settings.json 全プロジェクト 不可(個人)
.claude/settings.json そのプロジェクトのみ 可(リポジトリにcommit)
.claude/settings.local.json そのプロジェクトのみ 不可(gitignore推奨)

チームで共有したいルール(テストを通さないとコミットさせない、など)は .claude/settings.json に書いてcommitします。一方、自分専用の通知設定やパス依存の値は .claude/settings.local.json に分けると扱いやすくなります。

まとめ

CLAUDE.mdだけで運用しているとルールが増えるほど指示が薄まり、肝心な部分まで読まれにくくなります。言語固定・危険コマンド遮断・フォーマッタ自動適用といった機械的に毎回適用したい類は、hooks側に逃がした方がCLAUDE.mdを本来の指示だけのスリムな状態に保てます。3階層ネストは最初こそ戸惑いますが、event → matcher → handlersの順で見れば追えるようになります。細かいイベント一覧やJSON出力の仕様は公式リファレンス[1:2]を直接参照するのが最短です。

脚注
  1. Hooks reference - Claude Code Docs (2026-05-14 アクセス) ↩︎ ↩︎ ↩︎