cc-hooks について
Claude Code のライフサイクルイベントで実行するシェルコマンドを設定し、範囲を絞った検証可能なルールを適用するスキルです。標準のポリシーディスパッチャーと任意のガードを備え、ツール呼び出しを乱さないよう成功時は出力しません。
主な機能
- 01ライフサイクルイベントのフック
- 02ツール実行前のコマンド拒否と転送
- 03ポリシーレジストリーとディスパッチャー
- 04任意の読み取り・スキル編集ガード
- 05成功時の無出力と明示的な拒否
活用シーン
- 01危険なコマンドの実行前ブロック
- 02インストール済みスキルの編集をソースへ誘導
- 03過剰なファイル読み取りの制限
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ boshu2/ agentops --skill cc-hooksインストール後、Claude Code で/cc-hooksを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
skill.spec.jsonhooks/codex-read-budget-guard.shinstalled-skill-edit-guard.shpolicy-dispatch.shread-budget-guard.shskill-first-coord-guard.shpolicies/policies.jsonreferences/DCG-RCH.mdGUARDRAIL-VALUE-PROOF.mdHOOK-EVENTS.mdINSTALLED-SKILL-EDIT-GUARD.mdJSON-OUTPUT.mdPATTERNS.mdREAD-BUDGET-GUARD.mdSKILL-FIRST-COORDINATION-GUARD.mdscripts/install-hooks.shlint-policies.sh詳細
Claude Code のフック
Claude Code のライフサイクルの特定のタイミングで実行されるシェルコマンドです。
フックは、文章による指示ではできないことを機械的に強制します。モデルは指示を回避する方法を推論できても、終了コード 2 を回避する方法は推論できません。だからこそ、すべてのフックは対象を絞り、無言で、元に戻せるものでなければなりません。
名前付きの失敗モード — 成功時に余計な出力をする:終了コード 0 で標準出力を出すフックは、自分が保護しているツール呼び出しを壊します。成功時に何も出力しないことは、スタイル上の好みではなく契約の一部です。
プロンプト
Add a PreToolUse hook to fleet-router/.claude/settings.json that blocks `git push --force` on the main branch. Keep it silent on exit 0, exit 2 with a message on block, and confirm it fires with a manual test invocation before committing the change.
正常に動作している状態
- フックのスクリプトは、
git push --forceをブロックすると stderr にメッセージを出して終了コード2で終了し、許可する場合は標準出力を出さず終了コード0で終了します。 .claude/settings.jsonには、新しいフックのマッチャーエントリーが 1 つ追加され、既存のフックリストを置き換えず、その中に並びます。- 新しいマッチャーに対する手動テストを行うと、変更をコミットする前に、終了コード
2が見える状態で、トランスクリプトにブロックの実行が示されます。 - フックが検査するのは、自身が保護する
PreToolUse呼び出しだけで、ほかのファイルには一切手を加えません。
制約
- 強制フック(PreToolUse ポリシーディスパッチャー)はデフォルトで有効です。プラグインのインストール時には
hooks/hooks.jsonが自動的に接続され、スキルのコピーやチェックアウトでは、1 つのコマンド(scripts/install-hooks.sh)で接続されます。運用担当者はホストごとに無効化できます(/plugin disableを実行するか、設定のマッチャーを削除します)。 - 注入フック(SessionStart/UserPromptSubmit によるコンテキストの詰め込み)は無効のままにしてください。#511 の撤去では、常駐トークン数が 10.35M でも差分が 0 であることが証明されました。絶対に配布しないでください。フックなしでのコールドスタートゲートは、引き続きこれを強制します。
- 成功時は無言のままにし、イベントで定められた終了コード/JSON 契約に従ってのみブロックしてください。余計な標準出力はツール呼び出しを壊す可能性があります。
- Stop フックには
stop_hook_activeを指定して上限を設け、マッチャーの対象を絞って、再帰や無関係なコマンドの捕捉を防いでください。
クイックスタート
~/.claude/settings.json(ユーザー)または .claude/settings.json(プロジェクト)に追加します。
{"hooks":{"PreToolUse":[{"matcher":"Bash","hooks":[{"type":"command","command":"my-validator.sh"}]}]}}
フックイベント
| イベント | タイミング | ブロック | よくある用途 |
|---|---|---|---|
PreToolUse | ツール実行前 | はい | コマンドのブロック/変更 |
PostToolUse | ツールの実行成功後 | フィードバック | 自動フォーマット、リンター |
PermissionRequest | 権限ダイアログの表示時 | はい | 自動で許可/拒否 |
UserPromptSubmit | プロンプト送信時 | はい | コンテキストの追加、検証 |
Stop | Claude の処理終了時 | はい | 続行を強制 |
SessionStart | セッション開始時 | いいえ | コンテキストの読み込み、環境変数の設定 |
Notification | 通知時 | いいえ | デスクトップ通知 |
完全なスキーマ:HOOK-EVENTS.md
マッチャー
"Bash" → exact match
"Edit|Write" → regex OR
"mcp__.*__write" → MCP tools
"*" or "" → all tools
ツール:Bash、Read、Write、Edit、Glob、Grep、Task、WebFetch、WebSearch
終了コード
| コード | 効果 |
|---|---|
| 0 | 成功 — 標準出力から JSON を解析 |
| 2 | ブロック — stderr を Claude に渡す |
| その他 | ブロックしないエラー |
ツールのブロック
シンプル(終了コード 2):
echo "Blocked: reason" >&2 && exit 2
JSON(終了コード 0):
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Blocked"}}
判定:"allow"(自動許可)、"deny"(ブロック)、"ask"(ダイアログを表示)
入力の変更
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow",
"updatedInput":{"command":"modified-command"}}}
実例:DCG + RCH
{"hooks":{"PreToolUse":[{"matcher":"Bash","hooks":[
{"type":"command","command":"dcg"},
{"type":"command","command":"rch"}
]}]}}
- DCG:
git reset --hard、rm -rf、git push --forceをブロックします - RCH:ビルドをリモートワーカーに振り分けます
詳細:DCG-RCH.md
スキル優先の連携ガード(オプトイン)
エージェントがam/atm/ntm/tmux send-keysのCLIを独自に手作業で組み立てる前に、連携スキルを読み込むよう促すPreToolUseレシピです。このレシピは何も自動インストールしません。ホストごとにオプトインします(デフォルトで同梱されるポリシーディスパッチャーとは異なります)。
**フックのコンテキスト予算に関する原則:**フックは最も強力な強制手段(機械的に適用され、推論で回避できません)ですが、コンテキストを汚染するため、慎重に使用してください。フックは問題のない経路では必ずサイレントにし(終了コード0、stdout/stderrなし)、実際の違反があった場合にのみ(できればセッションごとに1回、センチネルで制御して)発火させ、ターンごとに注入するUserPromptSubmit/SessionStartよりもPreToolUseの違反ガードを優先してください。また、終了コード0のPreToolUse経路では、余計なstdoutを絶対に出力しないでください(JSONとして解析され、ツール呼び出しが壊れます)。終了コード2+stderrでブロックします。
このレシピには、両方のスクリプトのそのままのコピー、正確な先頭部分のみを対象とするマッチャー(br create --body "...am/atm/ntm..."が誤って発火しないようにするためのもの)、2つのマッチャーを使うオプトイン用のsettings.jsonスニペット、および発火・サイレントの全ケースを証明するbatsテストが含まれています。
レシピ:SKILL-FIRST-COORDINATION-GUARD.md
インストール済みスキルの編集ガード(オプトイン)
インストール済みスキルのコピー(*/.claude/skills/**、.codex、.gemini)を編集しようとした場合に、リポジトリ内の正本であるskills/<name>/へ誘導するPreToolUseのEdit|Writeガードです。これは正真正銘のミストークンです。インストール済み/シンボリックリンクされたコピーを編集する正当なケースはありません(インストール時に上書きされるか、シンボリックリンクを通じてファクトリのチェックアウトが編集されるためです)。誤検出の余地はありません。tool_input.file_pathのみを照合するため、本文でclaude/skillsに言及しているだけのドキュメントでは発火しません。元に戻せるため、ハードブロックではなく誘導します(終了コード2+1行のリダイレクトメッセージ)。それ以外のすべての経路ではサイレントで、セッションごとに1回発火します。標準では無効で、オプトイン用インストーラーが用意されています。
scripts/install-installed-skill-edit-guard.sh # user scope; --project for project
レシピ:INSTALLED-SKILL-EDIT-GUARD.md
効果の実証(フックを使わない運用に移行してもこのガードが残る理由)
要となるガードには、ゲートに依存しない発火ごとのテレメトリーが含まれています。発火するたびに、${AGENTOPS_HOME:-~/.agents/ao}/guardrail-telemetry.jsonlにJSONLを正確に1行追記します(AGENTOPS_GUARDRAIL_TELEMETRYで変更可能)。内容は{ts, session, token_class, path_sha256}です。パスはプライバシー保護のためSHA-256でハッシュ化され、生の値は記録されません。問題のない経路では何も書き込まれず、センサーはガードがインストールされて発火するまで動作しません。事前登録された方法論では、指標として時間経過に伴う発火試行率の低下(リダイレクトでは偽装できない指標であり、循環的な独自実装率ではありません)、最小N、ノイズフロアを定め、さらにNが小さい場合に結果が出ないことも許容できるとしています。これにより、2.xのフックが廃止された理由(#511)となった、ADR-0002 l.58の要件「肯定的な価値を示すテストまたは評価の証拠」を満たします。
Read-Budget Guard(オプトイン)
PreToolUse Read|Bash ガードです。上限のない読み取りで行数予算(AOP_READ_BUDGET_LINES、デフォルトは 350)を超える操作を拒否します。対象は limit を指定しない Read、または実効行数が上限を超える cat/head/tail です。Spotify で判明した問題は、CLAUDE.md に同じルールを記載しても推奨事項として扱われ、無視されていたこと、さらに予算超過の読み取りでは、その行が以降のターンごとに再送信されることです。判定条件は、指定された引数そのものに対する wc -l を使った LOOKUP です。そのため、単独のガードとして動作し、レジストリーポリシーにはなりません。limit で範囲を指定したスライス、予算以下のファイル、パイプ、リダイレクト、cat への言及にすぎない引用テキストは通過します。リテラルの引用符付き/エスケープされたパスは維持されます。未対応のシェル構文やディレクトリ変更を含むチェーンは、フェイルオープンになります。負の head 行数では、GNU head で実際に保持される行数を使います。拒否された Darwin のシステムユーティリティフラグは、実行ファイルの識別を確認した後に通過します。符号付きの正の head 行数は、その数値によって上限が決まります。コンテキストに入ったバイトは取り消せません。そのため、すべての試行をブロックします(終了コード 2 + stderr)。セッション中の初回は、正しい対応を 2 つ(範囲を指定して読み取るか、プラグインの agentops:bulk-reader サブエージェント/agentops:bulk-read ワークフローに委任する)示す完全なメッセージを出し、以降は短い行を出します。ランタイムに一覧表示されている場合に限り、単独の定義またはリンクでは名前のみを使ってください。AOP_WAIVE=core.context:unbounded-read で 1 回免除できます。ハッシュ化されたテレメトリーには、tool、lines、budget に加えて、ディスパッチャーの mode/decision の組み合わせが追加されます。初期状態では無効です。オプトインするインストーラー:
scripts/install-read-budget-guard.sh # user scope; --project for project
Policy Dispatch Engine(デフォルトで有効)
アドミッション制御レイヤー(エピック age-4qw1)です。N 個の手動配線された設定エントリーの代わりに、ポリシーをデータとして扱うレジストリー(policies/policies.json、契約 schemas/hooks-manifest.v2.schema.json)を評価する、1 つの PreToolUse ディスパッチャー(hooks/policy-dispatch.sh)です。これはツール呼び出しの高さにある膜です。語彙は同じですが、プッシュ時の爪/ゲートより低い位置にあります。
ポリシーごとに、dcg スタイルの ID(domain.object:token)、mode: deny | route | audit、マッチャー(ツールと command/file_path の正規表現)、正しいツールを明示する route_message、根拠、事前登録された value_proof を指定します(ADR-0002 の存続期間リースに基づき、証拠が蓄積されないポリシーは廃止します)。
述語の規律はスキーマで強制されます(#511 の反面教師)。deny/route が許可されるのは、コマンドまたはファイルパスに含まれる構文上の誤りトークンのみを判定する predicate_class: pure に限られます。LOOKUP/状態依存の述語は、レビュー済みの発火実績をもとに昇格されるまで audit 専用です。scripts/lint-policies.sh がこれを機械的に強制します(jq のみを使用。bats と CI で実行)。
許容する誤検出の範囲: 純粋な述語は生のコマンド文字列内のどこにでもあるトークンに一致するため、保護対象のトークンがデータとして引用されていても(コミットメッセージ本文、dcg test "..." のプローブ、ヒアドキュメントのペイロードなど)、有害な処理が実行されない場合でも発火することがあります。これは、純粋な述語だけが拒否できるというルールの意図的な代償です。代替手段であるリポジトリー/コンテキストの検索は、まさにこの規律が deny で禁止している状態依存の述語です。発火はすべて元に戻せます。AOP_WAIVE=<policy-id> で 1 回免除するか、policy-waivers の行を追加すれば解除できます。
セマンティクス:正常時は終了コード 0 で、出力はありません。deny は終了コード 2 と stderr のルート案内 1 行です(セッション中の初回は完全なメッセージ、以降は短い行を出しますが、すべての試行をブロックします)。route は終了コード 0 と permissionDecision:"ask" の JSON を返します。audit は許可して記録します。発火するたびに、ハッシュ化されたガードレールテレメトリーの行を 1 行追記します(token_class はポリシー ID。mode/decision も含みます)。AOP_WAIVE=<policy-id> で 1 回免除するか、policy-waivers ファイルに <policy-id> <expiry-epoch> の行を追加してください。レジストリーが見つからない場合、または jq がない場合はフェイルオープンになります。
強制対象のコホート(いずれも純粋な正規表現で、違反時の影響が大きいもの)。最初の 4 件は初日に担当するメンテナーのコホート(age-wnyt)で、このリポジトリーの成果物を保護します。5 件目は製品そのものの不変条件を保護するため、このリポジトリーだけでなく、すべての利用者のリポジトリーで発火します。
| ポリシー | ブロックする操作 | 案内先 |
|---|---|---|
core.git:add-beads-ledger | _beads/ を指定した git add(プライベート台帳の漏えいは一方向です) | 台帳リポジトリ自体を push します。公開ツリーでは決して git add _beads を実行しないでください |
core.provenance:ledger-hand-append | docs/provenance/ledger.jsonl へのリダイレクト/tee/Edit/Write(ハッシュチェーンで保護され、封印済みです) | ao provenance add |
core.skills:copy-into-installed | `~/.claude | .codex |
core.skills:edit-installed-copy | インストール済みスキルコピーへの Edit/Write(file_path のみ対象です。文章に反応することはありません) | リポジトリの skills/<name>/ を編集します |
core.verdicts:hand-edit | .agents/ao/verdicts/ への Edit/Write、または Bash の >/>>/tee/cp/rsync/mv(宛先位置を検証します)。ファイル名はコンテンツの SHA-256 そのものなので、手動編集するとダイジェストとの同一性が崩れます | 検証を再実行し、新しいアーティファクトを永続化します(validate.py store-verdict) |
core.verdicts:hand-edit は、対象がリポジトリではなく 約束 である唯一のポリシーです。ファイル名とハッシュが一致しなくなった判定は偽造された証拠であり、ツール呼び出しの階層ではこれを検出できません。ストアの読み取りには影響しません。判定に対する cat/ls/jq/rg/diff や、確認のために外部へコピーする操作は発火しません。ストア内へ書き込む操作のみが対象です。インプレースエディター(sed -i、perl -pi/-ni)と削除コマンド(rm、unlink、shred)も含まれます。フラグトークンとして照合するため、スクリプトテキストに -i が含まれているだけの読み取りでは発火しません(両方向の動作を bats で確認済みです)。開示済みの残るギャップ:noclobber を上書きするリダイレクト(>|)。
ユーザーへの提供方法 — すべてのインストール方法でフックが提供されます:
| インストール方法 | 提供方法 |
|---|---|
Claude Code プラグイン(claude plugin install agentops@agentops-marketplace) | 自動 — プラグインに hooks/hooks.json(${CLAUDE_PLUGIN_ROOT} パス)が含まれており、インストール時にフックが有効になります。配線作業は不要です |
npx skills@latest add boshu2/agentops/skills.sh によるコピー | スキルパッケージに専用インストーラーが含まれています:~/.claude/skills/cc-hooks/scripts/install-hooks.sh(1 コマンドで実行できます。ファイルコピーだけでは自動的に配線されません) |
| git clone/brew checkout | scripts/install-policy-dispatch.sh(同じスキル内蔵インストーラーに委譲します) |
インストーラーは配線前にレジストリを lint し、設定をバックアップして、冪等に動作します。ホストごとに無効にするには、/plugin disable agentops を実行するか、設定から 2 つの PreToolUse マッチャーを削除してください。
コントラクトテスト:tests/scripts/policy-dispatch.bats(各ポリシーのブロック+メッセージ+テレメトリー、予期しない標準出力の危険性、免除、監査/ルートモード、フェイルオープン)。
独自のフックを書く
最小限の Python:
#!/usr/bin/env python3
import json, sys
data = json.load(sys.stdin)
cmd = data.get('tool_input', {}).get('command', '')
if 'dangerous' in cmd:
print("Blocked: dangerous", file=sys.stderr)
sys.exit(2)
sys.exit(0) # Allow
フック入力(stdin):
{"tool_name":"Bash","tool_input":{"command":"npm test"},"session_id":"...","cwd":"..."}
環境変数
| 変数 | スコープ | 用途 |
|---|---|---|
CLAUDE_PROJECT_DIR | すべて | プロジェクトのルート |
CLAUDE_ENV_FILE | SessionStart/Setup | 環境変数を永続化します |
Stop フック(強制的に続行)
{"decision":"block","reason":"Tests failing. Fix before stopping."}
重要: 無限ループを防ぐため、stop_hook_active を確認してください。
アンチパターン
| してはいけないこと | 推奨される方法 |
|---|---|
| 古いオブジェクト形式 | matcher を含む配列形式 |
$VAR を引用符で囲まない | "$VAR" |
| JSON を出力して Exit 2 | Exit 2 では stderr のみを使用します |
stop_hook_active の確認を省略する | Stop フックでは必ず確認してください |
デバッグ
claude --debug # Hook execution details
/hooks # View/edit in REPL
出力仕様
- パス: ユーザーの
~/.claude/settings.jsonまたはプロジェクトの.claude/settings.jsonに加え、明示的に指定したフックスクリプトです。PreToolUse ポリシーディスパッチャーはデフォルトで提供されます(すべてのインストール方法で組み込まれています。「Policy Dispatch Engine」を参照してください)。追加のガードレシピ(スキル優先の調整、単独の installed-skill-edit、read-budget)は、オプトインするまで無効です。 - ファイル名:
settings.jsonはそのまま保持します。大きなシェルプログラムを JSON に埋め込むのではなく、スクリプトには内容がわかる実行可能なファイル名を付けてください。 - 形式: イベント配列、マッチャー、コマンドオブジェクトを使用した有効な Claude フック JSON です。フックの標準出力/標準エラーと終了コードは、選択したイベントスキーマに従います。
- 終了コード:
jq -e '.hooks | type=="object"' <settings.json>と、各マッチャーに対する代表的なサイレントテスト/発火テストで検証してください。解析エラー、正常時の不要な出力、または再帰のリスクがある場合は、有効化できません。 - 後続への引き継ぎ: 正確な適用範囲、元に戻すコマンド、テストの証拠、オプトインする場所が報告された後にのみ、オペレーターが使用します。
品質チェックリスト
- マッチャーは意図したイベント/入力で発火し、代表的な類似ケースでは出力しません。
- ブロック時と許可時の処理では、文脈を漏らすことなく、文書化された終了コードと出力先を使用します。
- フックは元に戻せるようにし、適用範囲を限定し、再帰を防止し、オプトインのホストポリシーであることを明示します。
参考資料
- HOOK-EVENTS.md - すべてのイベントと完全なスキーマ
- DCG-RCH.md - 本番環境での例(dcg、rch)
- INSTALLED-SKILL-EDIT-GUARD.md - インストール済みスキルの編集をリポジトリの skills/ に振り分けるオプトインガード(要)
- READ-BUDGET-GUARD.md - 行数の予算を超える無制限の読み取りを拒否するオプトインガード。bulk-read/code-write の委任と併用します
- GUARDRAIL-VALUE-PROOF.md - 事前登録された価値証明の手法と、発火ごとのテレメトリー契約(ADR-0002 l.58)
- PATTERNS.md - 自動フォーマット、ロギング、通知
- JSON-OUTPUT.md - 応答スキーマ