1
2

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 Hooks 実践パターン集【入門記事では教えてくれない7つの使い方】

1
Last updated at Posted at 2026-05-22

この記事は「Claude Code の Hooks を使ったら「確認待ち」が消えて開発速度が 2 倍になった話」の続編です。基本的な設定方法は前の記事を参照してください。

この記事でわかること

  • Hooks でよくある「動かない」「意図した挙動にならない」のデバッグ方法
  • 入力 JSON の全フィールドを使いこなす上級パターン
  • 複数 Hook を組み合わせてパイプラインを作る実践例
  • チーム開発で Hook を安全に共有する設計
  • pay-per-call-mcp と組み合わせた外部 API 自動呼び出しパターン

はじめに

前の記事に想定以上の反響をいただきました。

コメントやDMで多かった質問を整理すると:

  1. 「Hook スクリプトが動いてるか確認する方法がわからない」
  2. 「もっと複雑な条件分岐をしたい」
  3. 「チームで共有するときどうする?」
  4. 「PostToolUse の入力 JSON にどんなフィールドがあるか知りたい」

この記事はそれらをまとめた 続編・実践編 です。


Hook 入力 JSON の全フィールドリファレンス

Hook スクリプトが受け取る JSON は、フックの種類によって構造が違います。

PreToolUse / PostToolUse の入力

{
  "session_id": "abc123",
  "transcript_path": "/tmp/claude_transcript_abc123.jsonl",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm run test",
    "description": "Run tests"
  }
}

PostToolUse にはさらに tool_response が追加されます:

{
  "session_id": "abc123",
  "hook_event_name": "PostToolUse",
  "tool_name": "Edit",
  "tool_input": {
    "file_path": "/src/components/Button.tsx",
    "old_string": "...",
    "new_string": "..."
  },
  "tool_response": {
    "output": "File edited successfully"
  }
}

Stop の入力

{
  "session_id": "abc123",
  "transcript_path": "/tmp/claude_transcript_abc123.jsonl",
  "hook_event_name": "Stop",
  "stop_hook_active": false,
  "messages": [
    {
      "role": "assistant",
      "content": [
        {
          "type": "text",
          "text": "実装が完了しました..."
        }
      ]
    }
  ]
}

デバッグ方法

「Hookが動いているかわからない」は一番多い質問でした。

1. ログファイルに出力する

# ~/.claude/hooks/debug.sh
#!/bin/bash
INPUT=$(cat)
echo "$(date): $INPUT" >> ~/.claude/hooks/debug.log

これを PreToolUse に追加しておくと、Claude がツールを実行するたびに入力が記録されます。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "bash ~/.claude/hooks/debug.sh"
          }
        ]
      }
    ]
  }
}

2. Hook の返却値を確認する

Claude への返却値({"decision": "block"} など)が正しく送れているか確認:

#!/bin/bash
INPUT=$(cat)
RESPONSE='{"decision": "approve"}'
echo "$RESPONSE" | tee -a ~/.claude/hooks/responses.log
echo "$RESPONSE"

3. スクリプトを単体テストする

Hook スクリプトは通常のシェルスクリプトなので、Claude 抜きで直接テストできます:

echo '{"tool_name": "Bash", "tool_input": {"command": "rm -rf /"}}' \
  | bash ~/.claude/hooks/auto-approve-safe.sh

上級パターン 7 選

1. ツール名 × コマンド内容の複合条件

#!/bin/bash
INPUT=$(cat)

TOOL=$(echo "$INPUT" | python3 -c "import json,sys; print(json.load(sys.stdin).get('tool_name',''))")
COMMAND=$(echo "$INPUT" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('tool_input',{}).get('command',''))")
FILE=$(echo "$INPUT" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('tool_input',{}).get('file_path',''))")

# Bash ツールかつ安全なコマンドなら自動承認
if [ "$TOOL" = "Bash" ]; then
  case "$COMMAND" in
    npm\ run\ test*|npm\ run\ lint*|git\ status*|git\ diff*)
      echo '{"decision": "approve"}'
      exit 0
      ;;
  esac
fi

# Edit ツールで本番環境の設定ファイルは承認前に警告
if [ "$TOOL" = "Edit" ] && echo "$FILE" | grep -q "production\|prod\.env"; then
  echo '{"decision": "block", "reason": "本番設定ファイルの編集には手動確認が必要です: '"$FILE"'"}'
  exit 0
fi

2. Hook チェーン(複数処理を順番に実行)

一つのイベントに複数の Hook を登録すると順番に実行されます:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "bash ~/.claude/hooks/format.sh"
          },
          {
            "type": "command",
            "command": "bash ~/.claude/hooks/lint-check.sh"
          },
          {
            "type": "command",
            "command": "bash ~/.claude/hooks/update-imports.sh"
          }
        ]
      }
    ]
  }
}

フォーマット → リント → import 整理 が自動で走ります。

3. セッション単位でコンテキストを保持する

session_id を使ってセッションをまたいだ状態管理ができます:

#!/bin/bash
INPUT=$(cat)
SESSION_ID=$(echo "$INPUT" | python3 -c "import json,sys; print(json.load(sys.stdin).get('session_id','unknown'))")
COMMAND=$(echo "$INPUT" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('tool_input',{}).get('command',''))")

SESSION_LOG="/tmp/claude_session_${SESSION_ID}.log"

# このセッションで実行されたコマンドを記録
echo "$COMMAND" >> "$SESSION_LOG"

# 同じセッションで rm -rf を 2 回実行しようとしたらブロック
RM_COUNT=$(grep -c "rm -rf" "$SESSION_LOG" 2>/dev/null || echo 0)
if [ "$RM_COUNT" -gt 1 ]; then
  echo '{"decision": "block", "reason": "このセッションで rm -rf が複数回検出されました"}'
  exit 0
fi

4. 変更ファイルに応じて自動でテストスコープを絞る

#!/bin/bash
INPUT=$(cat)
FILE=$(echo "$INPUT" | python3 -c "
import json,sys
d = json.load(sys.stdin)
print(d.get('tool_input',{}).get('file_path',''))
")

if [ -z "$FILE" ]; then exit 0; fi

# ファイルパスからテスト対象を自動判定
if echo "$FILE" | grep -q "src/components/"; then
  COMPONENT=$(basename "$FILE" .tsx)
  npm run test -- --testPathPattern="$COMPONENT" --passWithNoTests 2>&1 | tail -3
elif echo "$FILE" | grep -q "src/api/"; then
  npm run test -- --testPathPattern="api" --passWithNoTests 2>&1 | tail -3
elif echo "$FILE" | grep -q "src/utils/"; then
  npm run test -- --testPathPattern="utils" --passWithNoTests 2>&1 | tail -3
fi

5. AI レビューを Hook に組み込む

PostToolUse でコード変更を GPT/Claude に自動レビューさせる:

#!/bin/bash
INPUT=$(cat)
FILE=$(echo "$INPUT" | python3 -c "
import json,sys
d = json.load(sys.stdin)
print(d.get('tool_input',{}).get('file_path',''))
")
NEW_STRING=$(echo "$INPUT" | python3 -c "
import json,sys
d = json.load(sys.stdin)
print(d.get('tool_input',{}).get('new_string','')[:500])
")

# 変更が大きいとき(50文字以上)だけレビューする
if [ ${#NEW_STRING} -gt 50 ] && echo "$FILE" | grep -qE '\.(ts|tsx|js)$'; then
  REVIEW=$(echo "$NEW_STRING" | \
    python3 -c "
import sys, subprocess, json
code = sys.stdin.read()
prompt = f'このコードに明らかな問題があれば1行で指摘して。なければ「OK」とだけ答えて:\n{code}'
# ここで Claude API / OpenAI API を呼び出す
print('OK')  # API 呼び出しに置き換える
")
  if [ "$REVIEW" != "OK" ]; then
    echo "⚠️ AI レビュー: $REVIEW" >&2
  fi
fi

6. Hook から Claude へフィードバックを返す

PostToolUse の Hook が標準出力に文字列を返すと、Claude がその内容を受け取って次のアクションに反映します:

#!/bin/bash
INPUT=$(cat)
FILE=$(echo "$INPUT" | python3 -c "
import json,sys
d = json.load(sys.stdin)
print(d.get('tool_input',{}).get('file_path',''))
")

# テスト結果を Claude に返してリトライさせる
if echo "$FILE" | grep -q "\.test\."; then
  RESULT=$(npm run test -- --testPathPattern="$(basename $FILE .test.ts)" 2>&1)
  if echo "$RESULT" | grep -q "FAIL"; then
    # Claude が受け取ってエラーを修正しようとする
    echo "テスト失敗: $RESULT"
  fi
fi

7. pay-per-call-mcp との連携で外部 API を自動呼び出し

Hook から LemonCake の MCP ツールを呼び出して、外部 API を自動実行:

#!/bin/bash
# PostToolUse: コードに T番号が追加されたら自動でインボイス検証
INPUT=$(cat)
NEW_STRING=$(echo "$INPUT" | python3 -c "
import json,sys
d = json.load(sys.stdin)
print(d.get('tool_input',{}).get('new_string',''))
")

# T番号のパターンを検出
T_NUMBER=$(echo "$NEW_STRING" | grep -oE 'T[0-9]{13}' | head -1)

if [ -n "$T_NUMBER" ]; then
  echo "💡 T番号を検出しました: $T_NUMBER"
  echo "check_tax ツールで検証することを推奨します"
fi

CLAUDE.md に以下を追記しておくと、Claude が自律的に check_tax を呼びます:

## Hook フィードバックへの対応

PostToolUse Hook が「T番号を検出」と返した場合、
必ず `check_tax` ツールで検証してから処理を続けること。

チーム開発での Hook 設計

ファイル構成の推奨

project/
├── .claude/
│   ├── settings.json          ← チーム共有(git管理)
│   ├── settings.local.json    ← 個人設定(gitignore)
│   └── hooks/
│       ├── pre-tool.sh        ← git管理
│       ├── post-edit.sh       ← git管理
│       └── notify.sh.example  ← テンプレート(実体はgitignore)
└── .gitignore
    # .claude/settings.local.json
    # .claude/hooks/notify.sh

チーム共有する Hook vs 個人設定にする Hook

Hook 共有すべき 理由
危険なコマンドのブロック ✅ チーム全体のリスク低減
コミット前のテスト実行 ✅ コード品質の統一
Prettier / ESLint ✅ フォーマット統一
Slack 通知 ❌ Webhook URL が人によって違う
自動承認ルール △ 信頼レベルが人によって違う

よくあるトラブルシューティング

Q. Hook が実行されているはずなのに効かない

exit 0 で終わっているか確認してください。{"decision": "approve"} を返さないと Claude は通常の確認フローに戻ります。Hook が何も出力しない場合は通常フローになります。

Q. python3 が見つからないエラー

PYTHON=$(which python3 || which python)
COMMAND=$(echo "$INPUT" | $PYTHON -c "...")

Q. Hook スクリプトが遅くて Claude の応答が止まる

重い処理(テスト実行など)は非同期で走らせて、結果だけ後から通知する:

#!/bin/bash
INPUT=$(cat)
# バックグラウンドで実行、完了したら通知
(npm run test 2>&1 | tail -3 | ~/.claude/hooks/notify-slack.sh) &
exit 0

Q. PostToolUse で tool_response が空

一部のツール(Read など)は tool_response を返さない場合があります。get() でデフォルト値を指定してください。


まとめ

パターン 効果
入力 JSON の全フィールド活用 ツール名・ファイルパス・変更内容で細かく制御
Hook チェーン フォーマット→リント→テストを1アクションで
セッション ID の活用 セッション単位での状態管理
AI レビューの組み込み 変更のたびに自動コードレビュー
Claude へのフィードバック Hook 結果を Claude が読んでリトライ
pay-per-call-mcp との連携 外部 API 呼び出しを完全自動化

Hooks は「Claude を監視する仕組み」ではなく「Claude の作業環境を整える仕組み」です。スクリプトを少しずつ育てていくと、気づいたら全部自動になっています。



📖 関連記事

自作 API を 1 行のミドルウェアで有料化したら AI エージェントが自動で支払ってくれた話

x402 プロトコルを使って自作 API を AI エージェント向けに有料化する方法。Stripe 不要、Provider 登録 5 分で開始できます。


試したい人へ

英語の Glama Playground が苦手な人は、以下のコマンドで日本のターミナルから動かせます:

npx -y pay-per-call-mcp@latest
# → 8 つのデモ API がすぐ使えます

設定不要、課金なし、サインアップ不要。

よくある質問(FAQ)

Q. Hook スクリプトのタイムアウトはありますか?
A. デフォルトは 60 秒です。重い処理はバックグラウンド実行(&)を使ってください。

Q. Windows でも動きますか?
A. PowerShell スクリプトを使えば動きます。command フィールドに powershell -File hook.ps1 と書きます。

Q. Hook からファイルを読み書きできますか?
A. できます。transcript_path からセッション履歴も読めます。ただし Hook からの書き込みは Claude の認識外になるため、重要なファイルは Claude 経由で変更することを推奨します。

Q. Hook のデバッグ中に Claude を止めたい
A. {"decision": "block", "reason": "デバッグ中"} を返せばツール実行をキャンセルできます。

1
2
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
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?