0:00 0:00
Article
Claude CodeのHooksで開発ワークフローを自動化する方法
Claude CodeのHooksの仕組みとイベント種別、settings.jsonでの設定例、PreToolUseで危険コマンドをブロックする方法、PostToolUseでlintを自動実行する方法、JSON出力でClaudeへフィードバックを返す方法、安全に運用するためのチェックリストまでを公式仕様に沿って整理します。
CLAUDE.mdは「お願い」、Hooksは「強制」です。「.envを読ませない」「rm -rfを実行させない」「編集後に必ずlintを走らせる」のように、確実に守らせたい挙動はCLAUDE.mdではなくHooksで実装します。本記事ではClaude CodeのHooksの仕組み・イベント種別・設定例・安全な運用方法を、公式仕様に沿って整理します。
結論:「守らせたい挙動」はHooksに寄せる
CLAUDE.mdに「rm -rfは禁止」と書いてもアドバイザリーで、AIが文脈次第で破ることがあります。Hooksは決定論的に動作するため、ルールを守らせたい場面では一段上の信頼性を得られます。具体的にはこういう使い分けです。
- CLAUDE.md:書き方ガイド、命名規則、設計の好み(やや破ってもいい)
- Hooks:実行可否の判定、ログ整理、自動lint、契約違反のブロック(破られたら困る)
ただしHooksは強力なぶん、設定ミスが連鎖事故になりやすい仕組みでもあります。安全運用のポイントは記事末尾のチェックリストに集約しました。CLAUDE.md側の書き方は「CLAUDE.mdテンプレート完全版」、セキュリティ全体像は「Claude Codeを使うときのセキュリティチェックリスト」に任せます。
Hooksの全体像:3つのカデンス
公式ドキュメントでは、Hooksイベントを発火タイミングで整理しています。
| カデンス | 主なイベント |
|---|---|
| セッション単位 | SessionStart / SessionEnd / Setup |
| ターン単位 | UserPromptSubmit / Stop / StopFailure |
| ツール呼び出しごと | PreToolUse / PostToolUse / PermissionRequest / PermissionDenied |
| 非同期 | Notification / SubagentStart / SubagentStop / PreCompact / FileChanged / CwdChanged ほか |
最初に触るべきは次の4つです。
PreToolUse:ツール実行前。ブロック・許可・差し戻しが可能(最も強力)PostToolUse:ツール実行後。lintや通知などの後処理(ブロック不可)UserPromptSubmit:ユーザー入力の送信前。プロンプトの拒否やコンテキスト追加が可能SessionStart:セッション開始時。git statusやブランチ名などをコンテキスト注入
PreToolUseのpermissionDecisionはallow / deny / ask / deferを返せます。「破壊的コマンドをdeny」「読み取り専用コマンドをallowで自動承認」のような自動化が組めます。
最小設定:settings.jsonにJSONで書く
Hooksは設定ファイルに JSON で記述します。配置場所で適用範囲が変わります。
| 場所 | 適用範囲 | チーム共有 |
|---|---|---|
~/.claude/settings.json | 自分のすべてのプロジェクト | しない |
.claude/settings.json | このプロジェクト | Git管理して共有 |
.claude/settings.local.json | このプロジェクト | gitignore推奨 |
| Managed settings | 組織全体 | 強制適用 |
最小構造はこうなります。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh"
}
]
}
]
}
}
matcherは対象ツール名のパターンで、"Bash"のような完全一致、"Edit|Write"のパイプ区切り、"mcp__memory__.*"のような正規表現も使えます。"*"または省略で全マッチです。
ハンドラのtypeはcommand(シェル)、http、mcp_tool、prompt、agentの5種類。最も使うのはcommandです。
例1:rm -rfをブロックするPreToolUse
最頻出のユースケースから入ります。.claude/hooks/block-rm.shに次のスクリプトを置きます。
#!/bin/bash
# 標準入力にイベントJSONが来る
COMMAND=$(jq -r '.tool_input.command // empty')
if echo "$COMMAND" | grep -qE '(^|[^a-zA-Z])rm[[:space:]]+(-[a-zA-Z]*r[a-zA-Z]*f|-[a-zA-Z]*f[a-zA-Z]*r)'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "rm -rf 系コマンドはHookでブロックされています。"
}
}'
exit 0
fi
exit 0
スクリプトは実行権限を付けます。
chmod +x .claude/hooks/block-rm.sh .claude/settings.jsonにHookを登録します。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh"
}
]
}
]
}
}
ifフィールドは権限ルールと同じ構文で「rmで始まるBash実行のときだけ走る」というプレフィルタです。Hook側でもう一度パターンを照合するのは、rm -rfをワンライナーに混ぜるbash -c 'cd /tmp && rm -rf foo'のようなケースを取りこぼさないためです。多重防御が基本です。秘密情報の3層防御は「Claude Codeに秘密情報を渡さないための実践ルール」で扱っています。
例2:Edit/Writeの後にlintを自動実行するPostToolUse
ファイル編集の直後にlintを走らせると、AIの「壊れたコードを残すまま次に進む」を抑止できます。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"if": "Edit(*.ts) | Edit(*.tsx) | Write(*.ts) | Write(*.tsx)",
"command": "/usr/local/bin/eslint",
"args": ["${tool_input.file_path}", "--fix"]
}
]
}
]
}
}
PostToolUseはブロック不可ですが、終了コード2を返すとstderrの内容がClaudeに次の入力として渡されます。lintエラーをClaudeに食わせて自動修正させる流れが組めます。lint結果が大量に出るときはgrepでerror行に絞ると、コンテキストを圧迫しません。
CLAUDE.mdの「アドバイザリーな規約」とHooksの「確定的なチェック」の使い分けは「Claude Codeにコード規約を守らせるための書き方」で扱っています。
例3:セッション開始時にコンテキストを注入する
朝にClaude Codeを起動した直後、毎回「現在のブランチは?」「未コミットは?」と聞き直すのは無駄です。SessionStartHookで自動的に状況を渡します。
#!/bin/bash
# .claude/hooks/load-context.sh
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown)
DIRTY=$(git status --short 2>/dev/null | wc -l | tr -d ' ')
jq -n --arg branch "$BRANCH" --arg dirty "$DIRTY" '{
hookSpecificOutput: {
hookEventName: "SessionStart",
additionalContext: ("Current branch: " + $branch + "\nUncommitted files: " + $dirty)
}
}'
設定はこうなります。
{
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/load-context.sh"
}
]
}
]
}
}
additionalContextに書いた文字列がそのままClaudeのコンテキストに追加されます。GitHub IssueのオープンPR数、過去24時間のSentryエラー件数など、毎回手で貼るのが面倒な「短いサマリ」をここに集約すると体感が変わります。コンテキスト消費の設計は「Claude Codeに必要な情報だけ渡すコンテキスト設計」を参照してください。
入出力仕様の要点
Hookに渡される共通フィールド
command型Hookは標準入力にJSONを受け取ります。
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.jsonl",
"cwd": "/current/directory",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": { "command": "npm test" }
}
jq -r '.tool_input.command'のように取り出して判定します。
終了コードの意味
| Exit Code | 意味 | JSON処理 |
|---|---|---|
| 0 | 成功 | 標準出力のJSONを解釈 |
| 2 | ブロック | stderrの内容をClaudeに返す |
| その他 | 非ブロックエラー | 無視(デバッグログのみ) |
JSON出力で返せる主なフィールド
| フィールド | 用途 |
|---|---|
continue | falseで会話を停止 |
stopReason | continue: false時の理由 |
systemMessage | 警告メッセージ表示 |
decision | block/allow |
hookSpecificOutput.permissionDecision | PreToolUse専用:allow/deny/ask/defer |
hookSpecificOutput.additionalContext | Claudeに追加のコンテキストを渡す(SessionStart等) |
危険な自動化と回避策
Hooksは強力ですが、設定ミスがそのまま事故になります。AIコーディング特有のリスクを5つ整理します。
| ミス | 何が起きる | 回避 |
|---|---|---|
PermissionRequestを全部Allow返し | 任意の破壊コマンドが通る | ifで対象を絞る、Bash(git *)のように許可は最小限 |
PostToolUseでgit pushまで走らせる | レビューなしでpushが発生 | PostToolUseは読み取り系・検査系に限定する |
PreToolUse内でAPIキーをecho | プロンプトに混入してログに残る | Hookスクリプトで秘密情報を出力しない |
一般的すぎるmatcher: "*" | 全ツールで余計な処理が走り遅延 | matcherは最も狭い範囲に |
自作Hookでexit 2を多用 | Claudeが無限リトライ | exit 2の前にメッセージを明確にし、再試行ループに入らないよう設計 |
bypassPermissionsモードと組み合わせる場合は、隔離環境(VM・コンテナ)でしか使わない原則を貫いてください。本番ターミナルや/配下まで触れる端末でHookとbypassを組み合わせるのは絶対に避けます。
/hooksメニューと管理
Claude Code内で/hooksと入力すると、現在有効なHooksの一覧をイベントごと・ソース別(User/Project/Local/Plugin/Session/Built-in)に確認できます。読み取り専用UIなので、編集はsettings.jsonを直接触ります。
Hooksをまるごと無効化したい場合はsettings.jsonに次を入れます。
{
"disableAllHooks": true
}
組織レベルでは管理者がallowManagedHooksOnlyを使い、ユーザー・プロジェクト・プラグインのHooksを禁止する設計も取れます(Managed settingsの仕組みで強制適用)。チーム導入時のルール設計は「チームでClaude Codeを導入するときに最初に決めるルール」で扱っています。
安全運用チェックリスト
-
PreToolUseはifで対象を絞り、許可範囲を最小化した -
rm -rfgit push --forcenpm publishをブロック対象に含めた -
PostToolUseは読み取り・検査系のみで、書き込みや送信を行わない - Hookスクリプトに実行権限を付け、
set -euo pipefailを入れた -
${CLAUDE_PROJECT_DIR}を使い、絶対パス埋め込みを避けた -
bypassPermissionsは隔離環境でのみ使う運用にした -
disableAllHooksの一時無効化手段をチームで共有した - Hookスクリプトに秘密情報を出力する箇所がない
-
.claude/settings.jsonをGit管理し、Hookの追加・変更はPRレビュー対象にした -
/hooksメニューで意図したHookだけが有効になっていることを確認した
次に読むおすすめ記事:
- 「Claude Codeを使うときのセキュリティチェックリスト」:HooksとPermissionの組み合わせ全体像
- 「Claude Codeにコード規約を守らせるための書き方」:CLAUDE.mdとHooksの分担
- 「Claude Codeに秘密情報を渡さないための実践ルール」:deny ruleとHookの多重防御