フックリファレンス
Claude Codeフックの詳細仕様と設計原則。
新規フック実装時の必須チェックリスト(クイックリファレンス)
新しいフックを作成する前に必ず確認すること。これを怠るとCIでブロックされる。
関数シグネチャ(よくある間違い)
| 関数 | 正しい呼び出し | よくある間違い |
|---|---|---|
make_approve_result | make_approve_result("hook-name") | make_approve_result() ← 引数必須 |
make_block_result | make_block_result("hook-name", "理由") | make_block_result("理由") ← 2引数必須 |
log_hook_execution | log_hook_execution(..., decision="block") | log_hook_execution(..., result="block") ← decision=が正しい |
JSON出力(必須)
# ❌ 悪い例: Python辞書をそのまま出力
print(make_approve_result("my-hook")) # {'decision': 'approve', ...}
# ✅ 良い例: json.dumpsでJSON文字列に変換
print(json.dumps(make_approve_result("my-hook"))) # {"decision": "approve", ...}
理由: Claude CodeはJSON形式を期待している。Python辞書形式(シングルクォート)は解析エラーになる。
テストファイル(CI必須)
新規フックには必ずテストファイルを作成する:
- •ファイル名:
.claude/hooks/tests/test_<hook名(アンダースコア区切り)>.py - •例:
issue-branch-check.py→test_issue_branch_check.py
CIの hook-test-requirement-check がテストなしの新規フックをブロックする。
最小実装テンプレート
#!/usr/bin/env python3
"""フックの説明."""
from __future__ import annotations
import json
from lib.execution import log_hook_execution
from lib.results import make_approve_result, make_block_result
from lib.session import parse_hook_input
def main() -> None:
"""メイン処理."""
hook_input = parse_hook_input()
tool_name = hook_input.get("tool_name", "")
# 対象外はスキップ
if tool_name != "Bash":
print(json.dumps(make_approve_result("my-hook")))
return
# チェックロジック
# ...
# ブロック時
print(json.dumps(make_block_result("my-hook", "ブロック理由")))
log_hook_execution(
hook_name="my-hook",
decision="block",
reason="ブロック理由",
)
if __name__ == "__main__":
main()
詳細は以下のセクションを参照:
フック出力フォーマット
フックはJSON形式で結果を返す:
| フィールド | 説明 |
|---|---|
decision | "approve" または "block" |
reason | ブロック理由(Claudeに送信) |
systemMessage | ユーザー表示メッセージ |
出力パターン設計
フックの出力は以下のパターンに従う(不要な出力を最小化):
| 状況 | JSON出力 | exit code | 理由 |
|---|---|---|---|
| 対象外 | なし | 0 | 対象外で出力するとログが煩雑 |
| 許可 | なし | 0 | 正常動作時は沈黙が原則 |
| ブロック | {"decision": "block", "reason": "..."} | 0 | 明示的な拒否メッセージが必要 |
| 通知 | {"decision": "approve", "systemMessage": "..."} | 0 | 許可しつつ情報を伝達 |
ヘルパー関数(lib/results.py):
- •
make_block_result(hook_name, reason): ブロック結果を生成 - •
make_approve_result(hook_name, message=None): 許可結果を生成
共通ライブラリ関数の詳細仕様(必読)
フック実装前に必ず確認すること。これを怠るとレビューで指摘される。
make_block_result(hook_name, reason)
自動的に行われる処理(手動で行う必要なし):
| 処理 | 詳細 |
|---|---|
| ログ記録 | log_hook_execution(hook_name, "block", reason) を内部で呼び出す(Issue #2023) |
| プレフィックス付与 | reasonに [{hook_name}] を自動追加 |
| 継続ヒント付与 | reasonに CONTINUATION_HINT を自動追加 |
| systemMessage生成 | ユーザー表示用のsystemMessageを自動生成 |
| stderr出力 | ❌ {hook_name}: {first_line} をstderrに出力 |
アンチパターン(やってはいけない):
# ❌ 悪い例: 重複呼び出し
log_hook_execution("my-hook", "block", reason) # 不要(make_block_resultが呼ぶ)
result = make_block_result("my-hook", reason)
# ❌ 悪い例: 重複プレフィックス
reason = f"[my-hook] {actual_reason}" # 不要(make_block_resultが付与)
result = make_block_result("my-hook", reason)
# ✅ 良い例: reasonのみを渡す
result = make_block_result("my-hook", "操作がブロックされました")
make_approve_result(hook_name, message=None)
仕様:
- •
decision: "approve"とsystemMessageを含む辞書を返す - •messageがNoneの場合、
✅ {hook_name}: OKがsystemMessageになる - •
reasonフィールドは含まれない(blockと異なる)
その他のヘルパー関数
| 関数 | 用途 |
|---|---|
print_continue_and_log_skip(hook_name, reason) | 対象外時の早期リターン(PreToolUse/PostToolUse用) |
print_approve_and_log_skip(hook_name, reason) | 対象外時の早期リターン(Stop hook用) |
check_skip_env(hook_name, env_var) | SKIP環境変数のチェックとログ記録 |
parse_hook_input() (lib/session.py)
フック入力の標準的な読み取り方法。以下を自動で行う:
- •stdinからJSONを読み取りパース
- •
session_idがあれば自動でset_hook_session_id()を呼び出す - •パースした辞書を返す(エラー時は空辞書)
戻り値構造(フックタイプ別):
| フックタイプ | 主要フィールド |
|---|---|
| PreToolUse / PostToolUse | tool_name, tool_input, session_id, cwd |
| UserPromptSubmit | user_prompt, session_id, cwd |
| SessionStart | session_id, cwd, source, transcript_path |
| Stop | stop_hook_active, session_id, cwd |
使用例:
from lib.session import parse_hook_input
def main():
hook_input = parse_hook_input() # session_idが自動設定される
tool_name = hook_input.get("tool_name")
tool_input = hook_input.get("tool_input", {})
command = tool_input.get("command", "")
# ... フックロジック
アンチパターン:
# ❌ 悪い例: 手動でJSONパースしてsession_id設定を忘れる data = json.loads(sys.stdin.read()) # session_idが設定されない → ログが正しくセッションに紐づかない # ✅ 良い例: parse_hook_input()を使用 hook_input = parse_hook_input() # session_id自動設定
HookContextへの移行(Issue #2449)
get_claude_session_id() はグローバル状態を使用するため、テスト困難性とスレッドセーフ性の問題がある。新しいフックは HookContext パターンを使用すること。
移行パターン:
# ❌ 旧パターン: グローバル状態を使用
from lib.session import parse_hook_input, get_claude_session_id
def main():
hook_input = parse_hook_input()
session_id = get_claude_session_id() # グローバル状態から取得
# ...
# ✅ 新パターン: HookContext(DI)を使用
from lib.session import parse_hook_input, create_hook_context
def main():
hook_input = parse_hook_input()
ctx = create_hook_context(hook_input)
session_id = ctx.get_session_id() # コンテキストから取得
# ...
メリット:
| 観点 | 旧パターン | 新パターン |
|---|---|---|
| テスト | モック困難(グローバル状態) | コンテキスト注入で容易 |
| スレッドセーフ | 問題あり | 問題なし |
| 依存関係 | 暗黙的 | 明示的 |
移行状況:
- •
get_claude_session_id()はdeprecated(SHOW_SESSION_DEPRECATION=1で警告表示) - •既存フックは段階的に移行中
- •新規フックは必ず
HookContextパターンを使用すること
フック実装前チェックリスト(共通ライブラリ)
- •
lib/results.pyのソースを確認したか - •
lib/session.pyのparse_hook_input()を使用しているか - •
create_hook_context()を使用しているか(get_claude_session_id()は非推奨) - • 使用する関数の副作用(ログ記録、プレフィックス付与)を理解したか
- • 重複処理(手動ログ記録、手動プレフィックス)をしていないか
設計判断の理由:
- •JSON出力はClaudeへのメッセージ表示用 → 重要な情報(ブロック・通知)のみ
- •ログ記録は監査・分析用 → 全実行を記録(対象外・許可含む)
- •「全て記録して、利用時にフィルター」の原則
ログ記録パターン:
| 状況 | decision値 | 例 |
|---|---|---|
| 対象外 | skip | "Not a merge command" |
| 許可 | approve | "All checks passed" |
| ブロック | block | "auto-merge blocked" |
systemMessage出力例
各フックのsystemMessage出力例。新規フック開発時の参考に。
task_start_checklist.py:
📋 **タスク開始前の確認チェックリスト** 以下の点を確認してからタスクを開始してください: **要件確認**: [ ] 要件は明確か?曖昧な点があれば質問する [ ] ユーザーの意図を正しく理解しているか? **設計判断**: [ ] 設計上の選択肢がある場合、ユーザーに確認する [ ] 既存のコードパターン・規約を把握しているか? 💡 不明点があれば、実装前に必ず質問してください。
dependency_check_reminder.py:
📦 **依存関係追加を検出** パッケージ `react-query` を追加しようとしています。 **最新情報を確認してください:** 1. **Context7**: `react-query` のドキュメントを参照 - `mcp__context7__resolve-library-id` でライブラリIDを取得 - `mcp__context7__get-library-docs` でドキュメントを取得 2. **Web検索**: 最新バージョン・変更履歴を確認 - 「react-query latest version」で検索 💡 古いAPIや非推奨メソッドの使用を防ぐため、最新情報の確認を推奨します。
open_issue_reminder.py:
🚨 **高優先度Issue(優先対応必須)**: → #123: 本番環境でログイン失敗 [P1, bug] 📋 **未アサインのオープンIssue** (対応検討してください): - #456: ダークモード対応 [enhancement] - #789: パフォーマンス改善 [P2] 詳細: `gh issue list --state open`
出力設計ガイドライン:
| 項目 | 推奨 |
|---|---|
| タイトル | 絵文字 + 太字で目立たせる |
| 構造 | 箇条書き/チェックリスト形式 |
| 長さ | 5-15行(長すぎると読まれない) |
| アクション | 具体的な次のステップを提示 |
PreToolUse フック一覧
Edit/Write ブロック
- •トリガー: ファイル編集前
- •動作: main/masterでの編集をブロック、worktree外を警告
オープンIssueリマインド (open_issue_reminder.py)
- •目的: 未アサインIssue表示、競合防止
- •動作: セッション開始時(1時間間隔)に最初のBashでトリガー
タスク開始チェックリスト (task_start_checklist.py)
- •目的: タスク開始時の要件・設計確認漏れ防止
- •動作: セッション開始時(1時間間隔)に最初のEdit/Write/Bashでトリガー
- •表示内容: 要件確認、設計判断、影響範囲、前提条件のチェックリスト
- •ブロック: しない(systemMessageでリマインド表示のみ)
依存関係チェックリマインド (dependency_check_reminder.py)
- •目的: 依存関係追加時にContext7/Web検索を促す
- •動作:
pnpm add,npm install,pip install等のコマンド検出時にトリガー - •表示内容: Context7でのドキュメント確認、Web検索での最新情報確認を促すメッセージ
- •ブロック: しない(systemMessageでリマインド表示のみ)
- •重複防止: 同じパッケージには1セッション1回のみ表示
Issue自動アサイン (issue_auto_assign.py)
- •目的: 複数エージェントのIssue競合防止
- •動作:
git worktree addでブランチ名からIssue番号を検出し自動assign - •パターン:
feature/issue-123-desc,fix/123-bug,#123-feature
PRスコープチェック (pr_scope_check.py)
- •目的: 1 Issue = 1 PR ルール強制
- •動作:
gh pr createで複数Issue参照をブロック
Skill使用リマインド・強制フック
worktree作成・PR作成時のSkill参照を促す2つの補完的なフック。
workflow_skill_reminder.py(リマインド型)
- •目的: worktree/PR作成時に
development-workflowSkill参照をリマインド - •トリガー:
git worktree add,gh pr create検出時 - •動作: 警告のみ(systemMessageでリマインド表示、ブロックしない)
- •関連Issue: #2387
出力例:
📚 workflow-skill-reminder: worktree作成が検出されました。 【development-workflow Skill 参照リマインダー】 worktree作成時は `development-workflow` Skill を参照してください。 **確認すべき内容:** □ worktree作成直後のチェック(main最新との差分確認) □ `--lock` オプションの使用(他エージェントの削除防止) ...
skill_usage_reminder.py(強制型)
- •目的: Skill使用なしでのworktree/PR作成をブロック
- •トリガー:
git worktree add,gh pr create検出時 - •動作: セッション中のtranscriptを確認し、必要なSkillが使用されていなければブロック
- •関連Issue: #2355
補完関係
両フックは以下の補完関係にある:
| フック | チェック内容 | 動作 |
|---|---|---|
workflow_skill_reminder.py | リマインド表示 | 「Skillを参照すべき」とリマインド |
skill_usage_reminder.py | Skill未使用時にブロック | Skill未使用ならブロック |
フロー:
- •
git worktree addを実行しようとすると、同じ PreToolUse フェーズで2つのフックが起動する - •
workflow_skill_reminder.py: 常に"approve"を返しつつ、systemMessageで「development-workflow Skillを参照してください」とリマインドを表示 - •
skill_usage_reminder.py: transcript を確認し、指定された Skill が未参照であれば"block"を返し、参照済みであれば"approve"を返す
両フックは同じコマンドに対して並行して独立に実行され、workflow_skill_reminder.py のリマインドと skill_usage_reminder.py のブロック可否判定の結果が組み合わされて Claude に渡される。
設計意図: リマインドで気づかせ、無視した場合はブロックで強制。2段階の防御で「手順が身についている」という誤った判断を防止。
マージ安全性チェック (merge_check.py)
4つのチェック:
- •
gh pr merge --autoをブロック - •
requested_reviewersにCopilot/Codexがいたらブロック - •Issue参照なしで却下されたコメントをブロック
- •コメントなしResolveをブロック
却下検出キーワード: 「範囲外」「軽微」「out of scope」「defer」
CI待機チェック (ci_wait_check.py)
- •目的: CI監視を
ci_monitor.pyに一元化 - •ブロック:
gh pr checks --watch,gh pr view --json mergeStateStatus等
Codex CLIレビューチェック
- •logger:
codex review実行時にブランチ・コミットを記録 - •check:
gh pr create/git push時に現在コミットがレビュー済みか確認
Pythonコードチェック (python_lint_check.py)
- •目的: CI前にPythonスタイル違反を検知
- •動作:
git commitでステージされた.pyをuvx ruffでチェック
フック設計チェック (hooks_design_check.py)
- •目的: フック間の責務重複防止、品質チェック
- •動作: 新規フック追加時にSRPチェックリストを警告表示、
log_hook_execution()未使用をブロック
UI確認チェック (ui_check_reminder.py)
- •目的: UI変更後の目視確認漏れ防止
- •対象:
locales/*.json,components/**/*.tsx,routes/**/*.tsx,index.css - •確認記録:
python3 .claude/scripts/confirm_ui_check.py
Markdownサイズチェック (markdown_size_check.py)
- •目的: Markdownファイル肥大化防止
- •上限: 40KB(Claude Codeパフォーマンス影響閾値)
Worktree削除前チェック (worktree_removal_check.py)
- •目的: worktree削除前にアクティブ作業を検出し、セッション競合・破壊を防止
- •トリガー:
git worktree removeコマンド検出時
2段階チェック:
| チェック | 対象 | --force でバイパス |
|---|---|---|
| cwdチェック | 現在の作業ディレクトリがworktree内にあるか | 不可(常にブロック) |
| アクティブ作業チェック | 最新コミット・未コミット変更・stash | 可能 |
cwdチェックの重要性:
cwdがworktree内にある状態で削除すると、以降の全Bashコマンドが ENOENT エラーで失敗する。
セッション全体が壊れるため、--force でも絶対にバイパスできない。
設計レビュー結果:
| 観点 | 判断 | 理由 |
|---|---|---|
| 並行性 | 問題なし | 各セッションは独立してcwdをチェック |
| エッジケース | 対応済み | symlink→resolve()、permission denied→fail-close |
| 依存関係 | 問題なし | cwdチェックはgitコマンド不使用(純粋なパス比較) |
| 状態管理 | 適切 | OSErrorでfail-close(ブロック側に倒す) |
| セキュリティ | 対応済み | resolve()でパストラバーサル対策 |
| 拡張性 | 良好 | 各チェックが独立関数として実装 |
Fail-Close設計:
# check_cwd_inside_worktree() の例
try:
cwd = Path.cwd().resolve()
# ... パス比較 ...
except OSError:
# cwdが取得できない場合は安全側に倒す
return True # ブロック
不確実な状況(OSError等)では常に「ブロック」を選択。誤ブロックは回復可能だが、誤許可によるセッション破壊は回復不可能なため。
PostToolUse フック一覧
Issue AIレビュー (issue_ai_review.py)
- •目的: Issue作成後に自動でAIレビュー(Gemini/Codex)を実行
- •トリガー:
gh issue create成功後 - •動作: バックグラウンドでGemini/Codexレビューを実行し、結果をIssueコメントとして投稿
- •ブロック: しない(PostToolUseで非ブロッキング実行)
Worktree自動セットアップ (worktree_auto_setup.py)
- •目的: worktree作成後の依存関係自動インストール
- •トリガー:
git worktree add成功後 - •動作:
setup_worktree.shを自動実行(pnpm install等) - •ブロック: しない(PostToolUseで非ブロッキング実行)
ブロック改善リマインダー (block_improvement_reminder.py)
- •目的: 同一フックが連続ブロックした際にフック改善を促す
- •トリガー: Bashツール実行後、セッションのブロック履歴を確認
- •動作: 同一フックが3回連続でブロックしていたら、改善策をsystemMessageで表示
- •ブロック: しない(systemMessageでリマインド表示のみ)
- •重複防止: 同じフックには1セッション1回のみ表示
- •関連Issue: #2432
検討すべき改善策の例:
- •SKIP環境変数のサポート追加
- •拒否メッセージの改善(具体的な解決策を提示)
- •誤検知パターンの修正
出力例:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 💡 フック改善リマインダー: merge-check ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ このセッションで `merge-check` が3回連続でブロックしています。 **検討すべき改善策:** 1. **SKIP環境変数のサポート追加** - `SKIP_MERGE_CHECK=1` でバイパス可能に 2. **拒否メッセージの改善** - 具体的な解決策を提示 - 何をすべきか明確に説明 3. **誤検知パターンの修正** - 正当なケースをブロックしていないか確認 - 検出ロジックの精度を改善 詳細は `hooks-reference` Skill を参照してください。
Stop フック
cwd-check
- •目的: カレントディレクトリ消失検知
- •動作: セッション終了時にcwd存在確認
git-status-check
- •目的: 未コミット変更検知
- •動作: mainに未コミット変更があれば警告
reflection-prompt
五省ベースの自己評価(prompt型):
- •評価基準: 要件理解、実装品質、検証、対応、効率 + 仕組み化
- •動作:
- •重大な未完了タスク →
block - •教訓が見つかったが仕組み化されていない →
block - •タスク完了 →
approve
- •重大な未完了タスク →
Session ID取得の仕組み
フックはセッションIDを使用してセッション固有の状態を管理する。
取得優先順位
Issue #777でClaude Codeが直接session_idを提供するようになったため、シンプルな2段階:
| 優先度 | ソース | 説明 |
|---|---|---|
| 1 | hook/statusline input JSON .session_id | Claude Codeが提供(最も信頼性が高い) |
| 2 | fallback | Python: ppid-{PPID} / Bash: 空文字列 |
廃止済み: 環境変数(CLAUDE_SESSION_ID, CLAUDE_CONVERSATION_ID)、marker file
実装
Python(正式実装): common.py の get_claude_session_id()
from common import get_claude_session_id session_id = get_claude_session_id()
Bash: statusline.sh の get_session_id()
(パフォーマンス上、Python実装と同様の方針でbashに実装。ただしfallback時の挙動は異なり、Pythonは ppid-{PPID}、Bashは空文字列を返す)
DEBUGログ
CLAUDE_DEBUG=1 環境変数を設定すると、取得元がstderrに出力される:
[session_id] source=hook_input, value=abc123...
関連Issue
- •Issue #734: セッションごとのstate file分離
- •Issue #756: hook inputからsession_id取得
- •Issue #777: Claude Codeによるsession_id提供
- •Issue #779: 取得ロジックの統一・ドキュメント化
プロセス間状態共有
重要な制約
フックは別プロセスで実行されるため、以下の制約がある:
| 方式 | 動作 | 使用可否 |
|---|---|---|
| グローバル変数 | プロセス終了で消失 | ❌ |
| モジュールレベル辞書 | プロセス間で共有不可 | ❌ |
| ファイルベース | 永続化・共有可能 | ✅ |
| 環境変数 | 読み取り専用 | △ (読み取りのみ) |
よくある間違い(Issue #1617):
# ❌ 動作しない: プロセス終了で消失
_cache = {}
def get_cached_value(key):
if key not in _cache:
_cache[key] = expensive_operation()
return _cache[key]
実装前チェックリスト
フック/スクリプト実装前に確認:
- • プロセス間で状態共有が必要か?
- • 必要ならファイルベース永続化を使用
- • 複数プロセスからの同時書き込みは? → ファイルロック検討
ファイルベース永続化パターン
推奨: JSON Lines形式(.jsonl)で追記
from __future__ import annotations
import json
from pathlib import Path
from common import METRICS_LOG_DIR
# ディレクトリを事前に作成
METRICS_LOG_DIR.mkdir(parents=True, exist_ok=True)
LOG_FILE = METRICS_LOG_DIR / "my-data.jsonl"
def append_event(event: dict) -> None:
"""イベントを追記(必要に応じてファイルロックを併用)"""
# 注意:
# - 小規模なログの追記であれば、通常はファイルシステムの書き込みアトミック性で
# 十分なことが多く、必ずしもロックを導入する必要はありません。
# - 複数プロセスから高頻度に同一ファイルへ書き込む場合は、fcntl や filelock などに
# よるファイルロック、あるいは専用のログ集約プロセスや外部ストレージの利用を
# 検討してください。
with open(LOG_FILE, "a") as f:
f.write(json.dumps(event) + "\n")
def read_events() -> list[dict]:
"""全イベントを読み込み"""
if not LOG_FILE.exists():
return []
events = []
# 大きなログファイルでもメモリ効率よく読み込むため、行単位で処理する
with open(LOG_FILE, "r") as f:
for line in f:
if line.strip():
events.append(json.loads(line))
return events
既存の実装例:
- •
.claude/logs/metrics/block-patterns.jsonl- ブロック→成功パターン - •
.claude/logs/execution/hook-execution.log- フック実行ログ
関連Issue
- •Issue #1617: インメモリ状態管理の問題
- •Issue #1634: プロセス間状態共有の教訓
フック設計原則
- •単一責任: 1フック = 1責務
- •疎結合: フック間の依存最小化
- •パス解決:
$CLAUDE_PROJECT_DIRを使用 - •SKIP環境変数: 全ブロッキングフックは
SKIP_*環境変数でバイパス可能にする
SKIP環境変数の命名規則:
| フック | 環境変数 |
|---|---|
| worktree-removal-check | SKIP_WORKTREE_CHECK |
| issue-incomplete-close-check | SKIP_INCOMPLETE_CHECK |
| issue-review-response-check | SKIP_REVIEW_RESPONSE |
| planning-enforcement | SKIP_PLAN |
| codex-review-check | SKIP_CODEX_REVIEW |
実装パターン:
from common import extract_inline_skip_env, is_skip_env_enabled
SKIP_HOOK_NAME_ENV = "SKIP_HOOK_NAME"
# main()関数内でチェック(エクスポートされた環境変数とインライン両方をサポート)
# 1. エクスポートされた環境変数をチェック
if is_skip_env_enabled(os.environ.get(SKIP_HOOK_NAME_ENV)):
log_hook_execution("hook-name", "skip", "SKIP_HOOK_NAME=1: チェックをスキップ")
print(json.dumps({"decision": "approve"}))
return
# 2. インライン環境変数をチェック(例: SKIP_HOOK_NAME=1 gh issue close)
inline_value = extract_inline_skip_env(command, SKIP_HOOK_NAME_ENV)
if is_skip_env_enabled(inline_value):
log_hook_execution("hook-name", "skip", "SKIP_HOOK_NAME=1: チェックをスキップ(インライン)")
print(json.dumps({"decision": "approve"}))
return
アンチパターン:
- •❌ 既存フックに「ついでに」別機能を追加
- •❌ 1フックで複数の無関係なチェック
- •❌ ブロッキングフックでSKIP環境変数をサポートしない
- •✅ 新責務は新フックとして実装
警告 vs ブロックの判断基準
フック設計時に「警告で許可」か「ブロックで拒否」かを判断する基準。
| 条件 | 選択 | 理由 |
|---|---|---|
| 副作用のある操作を防ぎたい | ブロック | 警告後もコマンドが実行され、副作用は発生してしまう |
| 操作失敗でフロー問題が発生 | ブロック | 例: exit code 1でPostToolUseフックがスキップされる |
| 情報提供のみで操作は許可 | 警告 | ユーザーに判断を委ねる場合 |
| 軽微な問題の通知 | 警告 | 操作自体は問題なく完了する場合 |
実例(Issue #2286, #2293):
worktree内から gh pr merge --delete-branch を実行すると:
- •マージは成功する(副作用発生)
- •ブランチ削除は失敗する(使用中のため)
- •exit code 1が返る
- •PostToolUseフックがスキップされる
当初は「警告」で実装したが、これでは問題が解決しない:
- •警告が表示されても、コマンドは実行される
- •副作用(マージ)は既に発生済み
- •PostToolUseフック(振り返り等)が発火しない
判断フローチャート:
- •副作用のある操作を防ぎたいか?
- •はい:
ブロック - •いいえ:
- •操作失敗時にフロー問題が起きるか?
- •はい:
ブロック - •いいえ:
警告(または通知なし)
- •はい:
- •操作失敗時にフロー問題が起きるか?
- •はい:
コマンド実行パターン
フック内で外部コマンドを実行する際の標準パターン。
shell=True vs shell=False の使い分け
| 状況 | shell | 理由 |
|---|---|---|
リダイレクト(2>&1)を含む | True | シェルがリダイレクトを解釈 |
パイプ(|)を含む | True | シェルがパイプを解釈 |
環境変数展開($VAR)が必要 | True | シェルが展開 |
| 上記以外 | False | セキュリティと明確さのため |
推奨パターン
✅ shell=False(デフォルト):
# リストでコマンドを渡す(推奨)
result = subprocess.run(
["gh", "pr", "list", "--json", "number,title"],
capture_output=True,
text=True,
timeout=30
)
✅ shell=True(リダイレクト・パイプが必要な場合):
# 文字列でコマンドを渡す
result = subprocess.run(
"gh pr list --search 'author:@me' 2>&1",
shell=True,
capture_output=True,
text=True,
timeout=30
)
アンチパターン
❌ shell=Falseでリダイレクトを含む:
# 悪い例: 2>&1がghの引数として渡される
result = subprocess.run(
"gh pr list 2>&1".split(), # split()でリストに変換
capture_output=True,
text=True
)
# エラー: "2>&1" が gh のコマンド引数として解釈される
❌ shell=Falseで文字列を渡す:
# 悪い例: shell=Falseで文字列は非推奨
result = subprocess.run(
"gh pr list", # 文字列
shell=False, # shell=False
...
)
# エラー: FileNotFoundError: [Errno 2] No such file or directory: 'gh pr list'
common.pyのヘルパー関数
フック共通モジュールにはコマンド実行のヘルパーがあります:
from common import run_command
# 基本的な使用法(shell=False、リストで渡す)
result = run_command(["gh", "pr", "list", "--json", "number"])
# シェル機能が必要な場合
result = run_command("gh pr list 2>&1 | grep error", shell=True)
関連Issue
- •Issue #1106: locked-worktree-guardがシェルリダイレクトを誤認識
フック実装チェックリスト
新しいフックを実装する際は、以下のチェックリストを確認する。
正規表現設計
コマンド検出の正規表現を設計する際の考慮事項:
- • 環境変数プレフィックス:
SKIP_PLAN=1 git worktree addのようなインライン環境変数 - • パイプ連結:
cmd1 | cmd2パターン - • シェル連結:
&&,;,||によるコマンド連結 - • 引用符内のコマンド:
echo "git commit"のような文字列内のコマンド(誤検知防止)- •
lib/strings.pyのstrip_quoted_strings()を使用して引用符内を除去してから検査
- •
- • サブシェル:
$(cmd)や`cmd`内のコマンド
参考パターン:
# 環境変数プレフィックスとシェル連結を考慮した例 # (?:^|&&|\|\||;|\s+) でコマンド開始位置を特定(\s+ で複数空白に対応) pattern = r"(?:^|&&|\|\||;|\s+)(?:\w+=\S+\s+)*git\s+worktree\s+add"
入力処理
- • 空入力:
tool_inputが空の場合の処理 - • 不正形式: JSON構造が期待と異なる場合
- • 必須フィールド欠落:
tool_input.command等が存在しない場合 - • Fail-Close設計: 不確実な状況ではブロック側に倒す
- • hook_cwd取得: cwdに依存するフックは
input_data.get("cwd")を使用(Issue #1172)
セキュリティチェックリスト
フック実装時に確認すべきセキュリティ項目。特に外部由来のデータを使用する場合は必須。
| チェック項目 | 確認内容 | 対策例 |
|---|---|---|
| Path Traversal | session_id等をファイルパスに使用していないか | is_valid_session_id() でUUID形式を検証 |
| ファイルパス構築 | ユーザー由来のデータをパスに含める場合 | 許可リスト方式、正規表現でフォーマット検証 |
| コマンドインジェクション | subprocess等でユーザー入力を使用 | shell=False、引数はリスト形式 |
| 秘密情報の露出 | ログや出力に秘密情報が含まれないか | API key, token, passwordをマスク |
| パス正規化 | symlink経由でのアクセス | Path.resolve() で正規化 |
Path Traversal対策の実装例:
from lib.session import is_valid_session_id
def get_state_file(session_id: str) -> Path | None:
"""Get state file path with security validation.
Args:
session_id: The session ID (should be UUID format).
Returns:
Path to state file, or None if session_id is invalid.
"""
# Security: Validate session_id to prevent path traversal attacks
# e.g., "../../../etc/passwd" would be rejected
if not is_valid_session_id(session_id):
return None
return STATE_DIR / f"state-{session_id}.json"
アンチパターン:
# ❌ 悪い例: 検証なしでファイルパスに使用
def get_state_file(session_id: str) -> Path:
# session_id = "../../../etc/passwd" で任意ファイルアクセス可能
return STATE_DIR / f"state-{session_id}.json"
# ✅ 良い例: 検証してから使用
def get_state_file(session_id: str) -> Path | None:
if not is_valid_session_id(session_id):
return None # Invalid session_id
return STATE_DIR / f"state-{session_id}.json"
関連Issue: #2696, PR #2693
hook_cwdパターン:
input_data = parse_hook_input()
hook_cwd = input_data.get("cwd") # Claude Codeが提供するセッションの実cwd
# hook_cwd を base_cwd パラメータとして渡す(環境変数より優先される)
cwd = get_effective_cwd(command, base_cwd=hook_cwd)
テスト
- • 正常系: 期待するコマンドを正しく検出
- • 異常系: 不正入力でクラッシュしない
- • エッジケース: 環境変数プレフィックス、パイプ連結等
- • 誤検知防止: 類似コマンドや引用符内を誤検出しない
- • テスト手法の確認:
run_hook(subprocess)かdirect callか事前確認
テスト手法の選択:
| 方式 | モック可否 | 用途 |
|---|---|---|
run_hook()(subprocess) | ❌ 効かない | E2Eテスト、実際の動作確認 |
hook_module.main() 直接呼び出し | ✅ 効く | ユニットテスト、例外ハンドリング確認 |
# モックが必要なテストは直接呼び出しを使用
from unittest.mock import patch
def mock_func(*args, **kwargs):
raise FileNotFoundError("simulated error")
with patch.object(hook_module, "get_effective_cwd", side_effect=mock_func):
hook_module.main() # 例外発生時の動作をテスト
出力フォーマット設計
systemMessage出力を含むフックを実装する場合のチェックリスト:
- • 出力目的の明確化: 何を伝えたいか1文で説明できるか
- • 出力構造の設計: 絵文字タイトル + 箇条書き/チェックリスト形式
- • 出力長の確認: 5-15行を目安(長すぎると読まれない)
- • アクション提示: 次に何をすべきか具体的に示す
- • hooks-referenceへの追記: 出力例をドキュメントに追加
出力テンプレート:
def get_message() -> str:
"""Generate the systemMessage content."""
lines = [
"📋 **[タイトル]**",
"",
"[説明文]",
"",
"**[セクション1]**:",
" - [項目1]",
" - [項目2]",
"",
"💡 [アクション/ヒント]",
]
return "\n".join(lines)
精度向上のポイント:
- •研究によると、出力例を含めると精度が向上する場合がある
- •曖昧な指示より具体的なフォーマット指定が効果的
- •箇条書き/チェックリスト形式は解釈のばらつきを減らす
Skill整合性チェック(Issue #1196)
フック設計時に関連Skillのルールとの整合性を確認する。これを怠ると、Skillに記載されたルールがフックで強制されず、ルール違反が発生する。
必須チェック項目:
- • 関連Skill特定: このフックが関連するSkillを特定したか?
- •例:
bug_issue_creation_guard.py→code-reviewSkill - •例:
merge_check.py→code-reviewSkill - •例:
worktree_removal_check.py→development-workflowSkill
- •例:
- • ルール網羅: 関連Skillに記載されたルールを全てカバーしているか?
- •例:
code-reviewSkillに「テスト不足は同じPRで対応」とあれば、「テスト」パターンも検出必須
- •例:
- • パターン確認: フックの検出パターンがSkillの記述と一致するか?
- •例: Skillに「バグ、テスト不足、エッジケース」とあれば、全てパターンに含める
確認手順:
- •フックの目的を明確化(何を防止/検出するか)
- •関連するSkillを
.claude/skills/から特定 - •Skillに記載されたルール/条件を抽出
- •フックのパターン/ロジックが全ルールをカバーしているか確認
- •不足があればフックを拡張
失敗事例:
| 問題 | 原因 | 対策 |
|---|---|---|
| テスト不足Issueがフックをすり抜け | code-review Skillの「テスト不足」ルールを検出パターンに含めていなかった | パターンに「テスト」を追加 |
| エッジケースIssueがフックをすり抜け | code-review Skillの「エッジケース」ルールを検出パターンに含めていなかった | パターンに「エッジケース」を追加 |
| コード品質Issueがフックをすり抜け | Skillルール整合性チェックなしでフックを設計した | Skillルール整合性チェックを導入 |
docstringテンプレート:
""" Hook to [目的]. Related Skills: - code-review: [関連ルール] - development-workflow: [関連ルール] Detection patterns based on Skill rules: - Pattern A: [Skillルール1] - Pattern B: [Skillルール2] """
関連Issue
- •Issue #1085: 正規表現で環境変数プレフィックスを考慮していなかった事例
- •Issue #1172: hook_cwdを使用していなかったためcwd検出が失敗
- •Issue #1196: フック設計時のSkillルール整合性チェック
パターン検出フック作成ガイドライン
キーワードリストや正規表現パターンを使用してテキストを検出するフック(例: defer_keyword_check.py)を作成・変更する際のガイドライン。
実データ分析の重要性
仮説ベースでパターンを選定すると:
- •誤検知が多い: 実際には問題ないケースをブロック
- •漏れが多い: 本当に検出すべきパターンを見逃す
- •メンテナンス負荷: 後から修正が必要になる
必須チェックリスト
パターン検出フックの作成・変更時は以下を確認:
- •
実データソースを特定したか
- •GitHub PR comments:
gh api repos/{owner}/{repo}/pulls/{pr}/comments - •Issue comments:
gh api repos/{owner}/{repo}/issues/{issue}/comments - •セッションログ:
~/.claude/logs/*.jsonl
- •GitHub PR comments:
- •
実データからパターンを抽出したか
- •仮説ベースではなく実際のデータを分析
- •頻度・コンテキストを確認
- •最低10件以上の実例を収集
- •
作成したパターンをテストしたか
- •検出率(実際に検出すべきものを検出できているか)
- •誤検知率(検出すべきでないものを検出していないか)
- •目標: 検出率 > 90%、誤検知率 < 10%
分析ツール
.claude/scripts/analyze_pattern_data.py を使用:
# パターン検索(実データからマッチを確認) python3 .claude/scripts/analyze_pattern_data.py search \ --pattern "後で|将来|フォローアップ" \ --show-matches # 頻度分析(パターンの出現頻度を確認) python3 .claude/scripts/analyze_pattern_data.py analyze \ --pattern "スコープ外" \ --days 30 # パターンリスト検証(複数パターンの精度を一括チェック) python3 .claude/scripts/analyze_pattern_data.py validate \ --patterns-file my-patterns.txt
実装時のベストプラクティス
- •
パターンリスト変数の命名:
python# 明確な命名で目的を示す DEFER_KEYWORDS = [...] # 「後で」系キーワード SCOPE_OUT_PATTERNS = [...] # スコープ外パターン
- •
実データ分析の証跡をコメントに残す:
python# 実データ分析: PR comments from 2025-12-30 # 検出対象: Issue参照なしで使われると問題になるパターン # 分析結果: 30件中28件検出、誤検知2件 DEFER_KEYWORDS = [...]
- •
除外コンテキストを考慮:
python# 誤検知防止: コードブロック、ドキュメント参照、ルール説明 EXCLUDE_CONTEXTS = [ r"```", # コードブロック r"AGENTS\.md", # ドキュメント参照 ]
自動検出
hook_change_detector.py がパターン検出フックの変更を検知し、実データ分析チェックリストをリマインドします。
検出条件:
- •
*_KEYWORDS,*_PATTERNS,*_REGEX変数を含む - •正規表現パターンリストを含む
- •
re.compile()を含む
関連Issue
- •Issue #1910: AskUserQuestion検出フック
- •Issue #1911: 「後で」キーワード検出フック
- •Issue #1912: パターン検出フック作成時の実データ分析強制
ブロックパターン追加時のチェックリスト
新しいブロックパターンをフックに追加する際のチェックリスト。引用符内での誤検知問題を踏まえた防止策。
誤検知防止
- •
引用符内のパターン:
--body "..."や--title "..."内での言及を除外- •対策: 引用符内のコンテンツを除去するヘルパー関数を使用(下記実装例参照)
- •❌ 悪い例:
if "gh run watch" in command: - •✅ 良い例:
if "gh run watch" in strip_quoted_content(command):
- •
コメント内:
# command は使わないのような文脈- •対策: 行頭が
#の場合は無視する処理を検討
- •対策: 行頭が
- •
変数展開:
$commandが対象パターンを含む場合- •対策:
\bで単語境界を明確にする
- •対策:
- •
パイプ/リダイレクト:
echo "..." | grep ...- •対策:
strip_quoted_content()でカバー
- •対策:
パターン設計
- • 正規表現を使用する場合、エスケープ漏れがないか
- • 大文字小文字の区別が必要か確認
- • 複数行コマンド対応が必要か
テスト
- • 正常系: ブロックすべきコマンドがブロックされる
- • 誤検知テスト: 引用符内での言及がブロックされない
python
def test_approves_quoted_mention(self): """引用符内でのパターン言及は承認される.""" command = 'gh pr comment --body "使用禁止: gh run watch"' result = should_block(command) assert result is False - • テスト追加先:
.claude/hooks/tests/test_<hook名>.py
実装例
import re
# NOTE: この関数はドキュメント用の簡略化サンプルです。
# 実際のフック実装では、.claude/hooks/ci_wait_check.py 内の strip_quoted_content を参照してください。
# そちらは文字単位のパースにより、エスケープされた引用符や未閉じ引用符などのエッジケースに対応しています。
def strip_quoted_content(text: str) -> str:
"""引用符内のコンテンツを簡易的に除去する.
ダブルクォート/シングルクォートで囲まれた内容を空文字に置き換える。
例: 'gh pr comment --body "使用禁止: gh run watch"' -> 'gh pr comment --body ""'
注意: この実装は正規表現による簡略版であり、以下のエッジケースには対応していません:
- 文字列外のエスケープされた引用符(例: \"foo\")
- 閉じられていない引用符
実運用時は .claude/hooks/ci_wait_check.py の実装を使用してください。
"""
return re.sub(r'(["\'])(?:\\.|(?!\1).)*\1', r'\1\1', text)
def should_block(command: str) -> bool:
"""Check if command should be blocked."""
# 引用符内のコンテンツを除去してからチェック
clean_command = strip_quoted_content(command)
# ブロック対象パターン
blocked_patterns = [
r"\bgh\s+run\s+watch\b",
r"\bgh\s+pr\s+checks\s+--watch\b",
]
for pattern in blocked_patterns:
if re.search(pattern, clean_command):
return True
return False
関連Issue
- •Issue #1621: ブロックパターンチェックリストの追加
git rev-list差分チェックのガイドライン
git rev-list でブランチ間の差分をチェックする場合、以下の3ケースを必ずテストする。
必須テストケース
| ケース | 状態 | 期待動作 |
|---|---|---|
| ahead | ローカルがリモートより進んでいる | 通常は許可 |
| behind | ローカルがリモートより遅れている | ブロックまたは警告 |
| same | 同一コミット | 許可 |
アンチパターン
❌ ハッシュ不一致でブロック:
# 悪い例: aheadでも誤ってブロック
if local_hash != remote_hash:
return block()
✅ behind_countでブロック:
# 良い例: behindの場合のみブロック
behind_count = get_behind_count() # git rev-list main..origin/main
if behind_count > 0:
return block()
テストコード例
def test_approves_when_local_is_ahead(self):
"""ローカルが進んでいる場合は許可"""
with patch.object(hook, "get_behind_count", return_value=0):
# behind=0 means local is same or ahead
result = hook.main()
self.assertEqual(result["decision"], "approve")
def test_blocks_when_local_is_behind(self):
"""ローカルが遅れている場合はブロック"""
with patch.object(hook, "get_behind_count", return_value=3):
result = hook.main()
self.assertEqual(result["decision"], "block")
def test_approves_when_same(self):
"""同一コミットの場合は許可(behind_count=0で判定)"""
# Note: behind_countベースの実装では、aheadとsameは同じ条件(behind_count=0)
with patch.object(hook, "get_behind_count", return_value=0):
result = hook.main()
self.assertEqual(result["decision"], "approve")
関連Issue
- •Issue #755: worktree-main-freshness-checkでの発見
- •Issue #760: 本ガイドライン追加
フックコード更新タイミング
フックはセッション開始時にロードされ、セッション中の修正は反映されない。
| タイミング | 動作 |
|---|---|
| セッション開始時 | フックコードがロードされる |
| セッション中 | フックを修正・マージしても現セッションには反映されない |
| 次セッション | 修正済みコードが適用される |
影響
- •フックの修正後も、現セッションでは旧コードが動作し続ける
- •修正を検証する場合、新しいセッションを開始する必要がある
- •誤検知が発生しても、現セッション内で「なぜまだ動くのか」と混乱しやすい
対処法
- •修正検証: 新セッションで動作確認
- •現セッション: 誤検知は無視して作業続行(修正済みなら問題なし)
関連Issue
- •Issue #2120: lesson-issue-checkの誤検知修正(この問題を発見したきっかけ)
- •Issue #2124: 本ドキュメント追加
CWD問題の対処
cd でフックが見つからなくなる問題:
- •フック設定:
$CLAUDE_PROJECT_DIRを使用(設定済み) - •
cdを避ける:-C/--dirオプションを使用- •✅
pnpm add xxx -C frontend - •❌
cd frontend && pnpm add xxx
- •✅
- •問題発生時: セッション再起動が必要
Block評価・改善サイクル
フックによるブロックが妥当だったか評価し、誤検知の場合はフックを改善するサイクル。
ワークフロー
[Block発生] → [ログ記録] → [評価] → [分析] → [改善]
1. Blockログの確認
# 最近のブロック一覧 python3 .claude/scripts/block_evaluator.py list # 特定のblockを詳細表示 python3 .claude/scripts/block_evaluator.py evaluate <block_id>
2. Block妥当性の評価
評価オプション:
- •
valid- ブロックは正しかった(本来止めるべき操作) - •
false_positive- 誤検知(止めるべきではなかった) - •
unclear- 判断できない
# 対話的に評価 python3 .claude/scripts/block_evaluator.py evaluate <block_id> # ワンライナーで評価 python3 .claude/scripts/block_evaluator.py evaluate <block_id> \ -e false_positive \ -r "テストファイルなのにブロックされた" \ -i "テストファイルを除外すべき"
3. 評価サマリーの確認
python3 .claude/scripts/block_evaluator.py summary
出力例:
Hook Valid False+ Unclear FP Rate ---------------------------------------------------------------------- ci-wait-check 5 3 0 37.5% codex-review-check 10 1 0 9.1%
4. 誤検知パターンの分析
python3 .claude/scripts/analyze_false_positives.py # 特定のフックのみ分析 python3 .claude/scripts/analyze_false_positives.py --hook ci-wait-check
5. フック改善
分析結果に基づいてフックを改善:
- •改善用worktree作成
- •フックコード修正
- •テスト追加
- •PR作成・マージ
ログファイル
| ファイル | 内容 |
|---|---|
.claude/logs/hook-execution.log | 全フック実行ログ |
.claude/logs/block-evaluations.log | Block評価記録 |
評価タイミング
- •推奨: セッション終了時に未評価ブロックを確認
- •必須: 「誤検知では?」と感じた時に即評価
AIレビュー対応ガイドライン
Copilot/Codexレビューで頻繁に指摘されるパターンと、事前検出の仕組み。
よくある指摘パターン
| パターン | 事前検出 | 対処法 |
|---|---|---|
| docstring不足 | ruff D101-D103 | pyproject.tomlで有効化済み |
| シグネチャ変更時のテスト未更新 | signature_change_check.py | pre-pushで警告 |
| 署名なしのスレッド解決 | resolve-thread-guard | 署名フォーマット必須 |
docstringルール(D101-D103)
pyproject.tomlで以下のruffルールを有効化:
[tool.ruff.lint]
select = [
# ... 既存ルール ...
"D101", # Missing docstring in public class
"D102", # Missing docstring in public method
"D103", # Missing docstring in public function
]
[tool.ruff.lint.pydocstyle]
convention = "google"
ローカル確認:
uvx ruff check .claude/hooks/ .claude/scripts/ --select D101,D102,D103
シグネチャ変更チェック
signature_change_check.py (pre-push hook):
- •関数シグネチャ(引数・戻り値)の変更を検出
- •対応テストファイルが更新されていない場合に警告
- •警告のみ(ブロックしない)
Known Limitations:
- •単一行の関数定義のみ検出(複数行は未対応)
- •関数名の変更は検出対象外
レビュースレッド解決の署名
resolve-thread-guard で必須化されている署名フォーマット:
| パターン | 例 |
|---|---|
| 範囲外 | [対象外] 本PRの範囲外のため対応しない |
| 軽微 | [軽微] タイポ修正のため今回は見送り |
| 対応済み | [対応済み] コミット abc1234 で修正 |
| 別Issue | [別Issue] #123 で対応予定 |
署名なしでResolveすると merge_check.py でブロックされる。
関連Issue
- •Issue #1107: Copilotレビューエラー時の対応手順
- •Issue #1108: 関数シグネチャ変更時のテスト更新チェック
- •Issue #1113: Copilotレビュー指摘パターンの事前検出
フックテンプレート
新規フック作成時のボイラープレート。再作業を防ぐため、テストから先に書く(TDD)。
1. テストファイルを先に作成
# .claude/hooks/tests/test_my_hook.py
"""Tests for my_hook.py"""
import json
from unittest.mock import patch
import pytest
class TestMyHook:
"""Test cases for my-hook."""
def test_approves_when_not_target_command(self):
"""対象外コマンドは許可される."""
# Given
hook_input = {"tool_name": "Bash", "tool_input": {"command": "ls -la"}}
# When
with patch("sys.stdin") as mock_stdin:
mock_stdin.read.return_value = json.dumps(hook_input)
# フックをインポートして実行
# ...
# Then: 出力なし(対象外)
def test_blocks_when_target_command(self):
"""対象コマンドはブロックされる."""
# Given
hook_input = {"tool_name": "Bash", "tool_input": {"command": "target command"}}
# When / Then
# ...
def test_approves_when_skip_env_set(self):
"""SKIP_MY_HOOK=1 でスキップ."""
# Given
hook_input = {"tool_name": "Bash", "tool_input": {"command": "SKIP_MY_HOOK=1 target command"}}
# When / Then
# ...
def test_handles_empty_input(self):
"""空入力でクラッシュしない."""
# Given
hook_input = {}
# When / Then: 例外なく処理
def test_handles_invalid_json(self):
"""不正JSONでクラッシュしない."""
# ...
2. フック本体を実装
#!/usr/bin/env python3
"""My hook description.
What it does:
- Check A
- Block B
"""
import json
import os
import sys
from common import (
extract_inline_skip_env,
is_skip_env_enabled,
log_hook_execution,
make_block_result,
)
SKIP_ENV = "SKIP_MY_HOOK"
def should_block(command: str) -> tuple[bool, str]:
"""Check if command should be blocked.
Args:
command: The command string to check.
Returns:
Tuple of (should_block, reason).
"""
# 対象コマンドのチェックロジック
if "target pattern" in command:
return True, "この操作はブロックされました。"
return False, ""
def main() -> None:
"""Entry point for the hook."""
try:
data = json.load(sys.stdin)
except json.JSONDecodeError:
# Fail-open: JSONエラーは許可
return
tool_name = data.get("tool_name", "")
if tool_name != "Bash":
return # 対象外
command = data.get("tool_input", {}).get("command", "")
if not command:
return # 空コマンドは対象外
# SKIP環境変数チェック
if is_skip_env_enabled(os.environ.get(SKIP_ENV)):
log_hook_execution("my-hook", "skip", f"{SKIP_ENV}=1")
print(json.dumps({"decision": "approve"}))
return
inline_value = extract_inline_skip_env(command, SKIP_ENV)
if is_skip_env_enabled(inline_value):
log_hook_execution("my-hook", "skip", f"{SKIP_ENV}=1 (inline)")
print(json.dumps({"decision": "approve"}))
return
# メインチェック
should_block_result, reason = should_block(command)
if should_block_result:
result = make_block_result("my-hook", reason)
log_hook_execution("my-hook", "block", reason, {"command": command})
print(json.dumps(result))
return
# 対象外またはOK: 出力なし
if __name__ == "__main__":
main()
3. settings.jsonに登録
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 $CLAUDE_PROJECT_DIR/.claude/hooks/my_hook.py"
}
]
}
]
}
}
チェックリスト
- • テストを先に書く(TDD)
- • 最低3つのテストケース(正常・境界・エラー)
- • SKIP環境変数のサポート
- • Fail-open設計(エラー時は許可)
- •
log_hook_execution()でログ記録 - • docstring追加(D101-D103対応)
フック統計
| イベント | フック数 |
|---|---|
| SessionStart | 5 |
| PreToolUse (Navigation) | 1 |
| PreToolUse (Edit/Write) | 3 |
| PreToolUse (Bash) | 34 |
| PostToolUse (Bash) | 17 |
| PostToolUse (Edit) | 2 |
| PostToolUse (Read/Glob/Grep) | 2 |
| PostToolUse (WebSearch/WebFetch) | 1 |
| Stop | 13 |
| 合計 | 78 |
注: ユニークフック数は75種類。一部のフック(task-start-checklist等)は複数のトリガーで発動するため、発動ポイント数(78)とは異なる。