この記事は「Claude Code の Hooks を使ったら「確認待ち」が消えて開発速度が 2 倍になった話」の続編です。基本的な設定方法は前の記事を参照してください。
- pay-per-call-mcp: https://www.npmjs.com/package/pay-per-call-mcp
- Discord: https://discord.gg/JBekn3EF
この記事でわかること
- Hooks でよくある「動かない」「意図した挙動にならない」のデバッグ方法
- 入力 JSON の全フィールドを使いこなす上級パターン
- 複数 Hook を組み合わせてパイプラインを作る実践例
- チーム開発で Hook を安全に共有する設計
- pay-per-call-mcp と組み合わせた外部 API 自動呼び出しパターン
はじめに
前の記事に想定以上の反響をいただきました。
コメントやDMで多かった質問を整理すると:
- 「Hook スクリプトが動いてるか確認する方法がわからない」
- 「もっと複雑な条件分岐をしたい」
- 「チームで共有するときどうする?」
- 「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": "デバッグ中"} を返せばツール実行をキャンセルできます。