hook-developer について
Claude Code のフックを作成、トラブルシューティングする開発者向けのリファレンススキルです。イベントのスキーマ、処理のブロック、登録、フック種別、テスト、実装パターンを解説します。
主な機能
- 01フックイベントの入出力スキーマ
- 02処理のブロックとコンテキスト注入
- 03フック登録とマッチャーパターン
- 04コマンド型とプロンプト型のフック
- 05テストとデバッグの手引き
- 06一般的なフック実装パターン
活用シーン
- 01正しいイベントスキーマでフックを作成
- 02フックで処理をブロックできるか確認
- 03`settings.json` でイベントマッチャーを設定
- 04フックの出力や登録の問題を調査
MCP 対応
Claude Code のフックで MCP ツールを識別、マッチさせる方法と、MCP サーバーツール名のパターンを説明します。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ parcadei/ continuous-claude-v3 --skill hook-developerインストール後、Claude Code で/hook-developerを入力して読み込まれたか確認します。
詳細
フック開発者
Claude Code フック開発の完全なリファレンスです。正しい入出力スキーマでフックを作成する際にお使いください。
使用する場面
- 新しいフックを作成するとき
- フックの入出力形式をデバッグするとき
- 使用可能なフィールドを確認するとき
- settings.json でフックの登録を設定するとき
- フックでブロックできる処理とコンテキストを挿入できる処理を確認するとき
クイックリファレンス
| フック | 実行されるタイミング | ブロック可能? | 主な用途 |
|---|---|---|---|
| PreToolUse | ツールの実行前 | はい | ツール呼び出しのブロック/変更 |
| PostToolUse | ツールの完了後 | 一部可能 | ツールの結果への対応 |
| UserPromptSubmit | ユーザーがプロンプトを送信したとき | はい | 検証/コンテキストの挿入 |
| PermissionRequest | 権限ダイアログの表示時 | はい | 自動で許可/拒否 |
| SessionStart | セッションの開始時 | いいえ | コンテキストの読み込み、環境変数の設定 |
| SessionEnd | セッションの終了時 | いいえ | クリーンアップ/状態の保存 |
| Stop | エージェントの処理終了時 | はい | 継続の強制 |
| SubagentStart | サブエージェントの起動時 | いいえ | パターンの連携 |
| SubagentStop | サブエージェントの処理終了時 | はい | 継続の強制 |
| PreCompact | コンテキストの圧縮前 | いいえ | 状態の保存 |
| Notification | 通知の送信時 | いいえ | カスタムアラート |
フックの種類のオプション: type: "command"(bash)または type: "prompt"(LLM による評価)
フックの入出力スキーマ
PreToolUse
目的: ツールの実行前に、その実行をブロックまたは変更します。
入力:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "default|plan|acceptEdits|bypassPermissions",
"hook_event_name": "PreToolUse",
"tool_name": "string",
"tool_input": {
"file_path": "string",
"command": "string"
},
"tool_use_id": "string"
}
出力(JSON):
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow|deny|ask",
"permissionDecisionReason": "string",
"updatedInput": {}
},
"continue": true,
"stopReason": "string",
"systemMessage": "string",
"suppressOutput": true
}
終了コード 2: ツールをブロックし、stderr を Claude に表示します。
一般的なマッチャー: Bash、Edit|Write、Read、Task、mcp__.*
PostToolUse
目的: ツール実行の結果に対応し、Claude にフィードバックを提供します。
入力:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "PostToolUse",
"tool_name": "string",
"tool_input": {},
"tool_response": {
"filePath": "string",
"success": true,
"output": "string",
"exitCode": 0
},
"tool_use_id": "string"
}
重要: 応答フィールドは tool_result ではなく、tool_response です。
出力(JSON):
{
"decision": "block",
"reason": "string",
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "string"
},
"continue": true,
"stopReason": "string",
"suppressOutput": true
}
ブロック: "decision": "block" と "reason" を指定すると、Claude に問題への対処を促します。
一般的なマッチャー: Edit|Write、Bash
UserPromptSubmit
目的: ユーザープロンプトを検証し、Claude が処理する前にコンテキストを挿入します。
入力:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "UserPromptSubmit",
"prompt": "string"
}
出力(プレーンテキスト):
Any stdout text is added to context for Claude.
出力(JSON):
{
"decision": "block",
"reason": "string",
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "string"
}
}
ブロック: "decision": "block" を指定すると、プロンプトを消去し、"reason" をユーザーにのみ表示します(Claude には表示されません)。
終了コード 2: プロンプトをブロックし、stderr をユーザーにのみ表示します。
PermissionRequest
目的: 権限ダイアログの判断を自動化します。
入力:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "PermissionRequest",
"tool_name": "string",
"tool_input": {}
}
出力:
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow|deny",
"updatedInput": {},
"message": "string",
"interrupt": false
}
}
}
SessionStart
目的: セッションを初期化し、コンテキストを読み込み、環境変数を設定します。
入力:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "SessionStart",
"source": "startup|resume|clear|compact"
}
環境変数: CLAUDE_ENV_FILE — export VAR=value を書き込むと、環境変数を保持できます。
出力(プレーンテキストまたは JSON):
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "string"
},
"suppressOutput": true
}
プレーンテキストの stdout はコンテキストとして追加されます。
SessionEnd
目的: クリーンアップ、状態の保存、セッションの記録を行います。
入力:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "SessionEnd",
"reason": "clear|logout|prompt_input_exit|other"
}
出力: セッションはすでに終了処理中のため、影響を与えることはできません。クリーンアップにのみ使用してください。
Stop
目的: Claude が停止するタイミングを制御し、継続を強制します。
入力:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "Stop",
"stop_hook_active": false
}
重要: 無限ループを防ぐため、stop_hook_active: true を確認してください!
出力:
{
"decision": "block",
"reason": "string"
}
ブロック: "decision": "block" を指定すると、"reason" をプロンプトとして Claude の処理継続を強制します。
SubagentStart
目的: サブエージェント(Task ツール)の起動時に実行します。
入力:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "SubagentStart",
"agent_id": "string"
}
出力: コンテキストの挿入のみ(ブロックはできません)。
SubagentStop
目的: サブエージェント(Task ツール)が停止するタイミングを制御します。
入力:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "SubagentStop",
"stop_hook_active": false
}
出力: Stop と同じです。
PreCompact
目的: コンテキストの圧縮前に状態を保存します。
入力:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "PreCompact",
"trigger": "manual|auto",
"custom_instructions": "string"
}
マッチャー: manual、auto
出力:
{
"continue": true,
"systemMessage": "string"
}
Notification
目的: 通知をカスタム処理します。
入力:
{
"session_id": "string",
"transcript_path": "string",
"cwd": "string",
"permission_mode": "string",
"hook_event_name": "Notification",
"message": "string",
"notification_type": "permission_prompt|idle_prompt|auth_success|elicitation_dialog"
}
マッチャー: permission_prompt、idle_prompt、auth_success、elicitation_dialog、*
出力:
{
"continue": true,
"suppressOutput": true,
"systemMessage": "string"
}
settings.json での登録
標準的な構造
{
"hooks": {
"EventName": [
{
"matcher": "ToolPattern",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/my-hook.sh",
"timeout": 60
}
]
}
]
}
}
マッチャーパターン
| パターン | 一致するもの |
|---|---|
Bash | Bash ツールのみ |
Edit|Write | Edit または Write |
Read.* | 正規表現:Read* |
mcp__.*__write.* | MCP の書き込みツール |
* | すべてのツール |
大文字と小文字は区別されます: Bash ≠ bash
マッチャーが必要なイベント
- PreToolUse - はい(必須)
- PostToolUse - はい(必須)
- PermissionRequest - はい(必須)
- Notification - はい(任意)
- SessionStart - はい(
startup|resume|clear|compact) - PreCompact - はい(
manual|auto)
マッチャーが不要なイベント
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [{ "type": "command", "command": "/path/to/hook.sh" }]
}
]
}
}
フックの種類
コマンドフック(type: "command")
デフォルトの種類です。bash コマンドまたはスクリプトを実行します。
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/my-hook.sh",
"timeout": 60
}
プロンプトベースのフック(type: "prompt")
LLM(Haiku)を使用して、コンテキストを考慮した判断を行います。Stop/SubagentStop に最適です。
{
"type": "prompt",
"prompt": "Evaluate if Claude should stop. Context: $ARGUMENTS. Check if all tasks are complete.",
"timeout": 30
}
応答スキーマ:
{
"decision": "approve" | "block",
"reason": "Explanation",
"continue": false,
"stopReason": "Message to user",
"systemMessage": "Warning"
}
MCP ツールの命名規則
MCP ツールは mcp__<server>__<tool> のパターンを使用します:
| パターン | 一致するもの |
|---|---|
mcp__memory__.* | memory サーバーのすべてのツール |
mcp__.*__write.* | MCP のすべての書き込みツール |
mcp__github__.* | GitHub のすべてのツール |
環境変数
すべてのフックで利用可能
| 変数 | 説明 |
|---|---|
CLAUDE_PROJECT_DIR | プロジェクトルートの絶対パス |
CLAUDE_CODE_REMOTE | リモート/Web の場合は "true"、ローカルの CLI の場合は空 |
SessionStart のみ
| 変数 | 説明 |
|---|---|
CLAUDE_ENV_FILE | export VAR=value の行を書き込むパス |
プラグインフックのみ
| 変数 | 説明 |
|---|---|
CLAUDE_PLUGIN_ROOT | プラグインディレクトリの絶対パス |
終了コード
| 終了コード | 動作 | stdout | stderr |
|---|---|---|---|
| 0 | 成功 | JSON を処理 | 無視 |
| 2 | ブロッキングエラー | 無視 | エラーメッセージ |
| その他 | 非ブロッキングエラー | 無視 | 詳細モード |
フック別の終了コード 2 の動作
| フック | 効果 |
|---|---|
| PreToolUse | ツールをブロックし、stderr を Claude に送信 |
| PostToolUse | stderr を Claude に送信(ツールはすでに実行済み) |
| UserPromptSubmit | プロンプトをブロックし、stderr をユーザーのみに送信 |
| Stop | 停止をブロックし、stderr を Claude に送信 |
シェルラッパーパターン
#!/bin/bash
set -e
cd "$CLAUDE_PROJECT_DIR/.claude/hooks"
cat | npx tsx src/my-hook.ts
バンドル済みの場合は、次のようにします:
#!/bin/bash
set -e
cd "$HOME/.claude/hooks"
cat | node dist/my-hook.mjs
TypeScript ハンドラーパターン
import { readFileSync } from 'fs';
interface HookInput {
session_id: string;
hook_event_name: string;
tool_name?: string;
tool_input?: Record<string, unknown>;
tool_response?: Record<string, unknown>;
// ... other fields per hook type
}
function readStdin(): string {
return readFileSync(0, 'utf-8');
}
async function main() {
const input: HookInput = JSON.parse(readStdin());
// Process input
const output = {
decision: 'block', // or undefined to allow
reason: 'Why blocking'
};
console.log(JSON.stringify(output));
}
main().catch(console.error);
フックのテスト
手動テストコマンド
# PostToolUse (Write)
echo '{"tool_name":"Write","tool_input":{"file_path":"test.md"},"tool_response":{"success":true},"session_id":"test"}' | \
.claude/hooks/my-hook.sh
# PreToolUse (Bash)
echo '{"tool_name":"Bash","tool_input":{"command":"ls"},"session_id":"test"}' | \
.claude/hooks/my-hook.sh
# SessionStart
echo '{"hook_event_name":"SessionStart","source":"startup","session_id":"test"}' | \
.claude/hooks/session-start.sh
# SessionEnd
echo '{"hook_event_name":"SessionEnd","reason":"clear","session_id":"test"}' | \
.claude/hooks/session-end.sh
# UserPromptSubmit
echo '{"prompt":"test prompt","session_id":"test"}' | \
.claude/hooks/prompt-submit.sh
TypeScript の編集後に再ビルド
cd .claude/hooks
npx esbuild src/my-hook.ts \
--bundle --platform=node --format=esm \
--outfile=dist/my-hook.mjs
よくあるパターン
危険なファイルをブロックする(PreToolUse)
#!/usr/bin/env python3
import json, sys
data = json.load(sys.stdin)
path = data.get('tool_input', {}).get('file_path', '')
BLOCKED = ['.env', 'secrets.json', '.git/']
if any(b in path for b in BLOCKED):
print(json.dumps({
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": f"Blocked: {path} is protected"
}
}))
else:
print('{}')
ファイルを自動フォーマットする(PostToolUse)
#!/bin/bash
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
if [[ "$FILE" == *.ts ]] || [[ "$FILE" == *.tsx ]]; then
npx prettier --write "$FILE" 2>/dev/null
fi
echo '{}'
Git のコンテキストを注入する(UserPromptSubmit)
#!/bin/bash
echo "Git status:"
git status --short 2>/dev/null || echo "(not a git repo)"
echo ""
echo "Recent commits:"
git log --oneline -5 2>/dev/null || echo "(no commits)"
テストの検証を必須にする(Stop)
#!/usr/bin/env python3
import json, sys, subprocess
data = json.load(sys.stdin)
# Prevent infinite loops
if data.get('stop_hook_active'):
print('{}')
sys.exit(0)
# Check if tests pass
result = subprocess.run(['npm', 'test'], capture_output=True)
if result.returncode != 0:
print(json.dumps({
"decision": "block",
"reason": "Tests are failing. Please fix before stopping."
}))
else:
print('{}')
デバッグチェックリスト
- settings.json にフックが登録されていますか?
- シェルスクリプトに
+x権限がありますか? - TS の変更後にバンドルを再ビルドしましたか?
-
tool_resultではなくtool_responseを使用していますか? - 出力は有効な JSON(またはプレーンテキスト)ですか?
- Stop フックで
stop_hook_activeを確認していますか? - パスに
$CLAUDE_PROJECT_DIRを使用していますか?
過去のセッションから得た重要な知見
- フィールド名が重要です -
tool_resultではなくtool_response - 出力形式 - ブロックする場合は
decision: "block"とreason - 終了コード 2 - stderr は Claude/ユーザーに送信され、stdout は無視されます
- バンドルを再ビルドします - TypeScript ソースの編集は自動では反映されません
- 手動でテストします - フックに依存する前に
echo '{}' | ./hook.shを実行します - まず出力を確認します - コードを編集する前に
ls .claude/cache/を実行します - デタッチされた spawn はエラーを隠します - デバッグ用にログ出力を追加します
関連項目
/debug-hooks- 体系的なデバッグワークフロー.claude/rules/hooks.md- フック開発ルール