AI駆動開発の実運用ノート

配布資料の目次 › 設定カタログ

配布資料 2101 — hooks カタログ(全24本)

事実は 2026-07-17 時点の題材プロジェクトの実コード(settings.json の実配線 + 各スクリプト本文)に基づく。

フックは難しいものだと思われがちですが、中身はただのシェルスクリプトです。

条件を判定して、満たさなければエラーを出して終わります。それだけです。 それだけだからこそ、AI 自身に書かせられます。ここにある24本も、ほぼ全部 AI が書きました。

このカタログは全24本の目的・動作機構・誕生の経緯・バイパス条件を並べたものです。 実際の書き方は最後の「フックの書き方」に置きました。読む順はどちらからでも構いません。

0. 先に読む — 7つの設計哲学

1. ルールは文書ではなくフックで強制します。 文書だけのルールは AI も人間も破ります。破れない仕組みにして初めてルールになります。

2. 多層防御にします。 同じ規律を SessionStart(開始時に文脈へ在中)→ UserPromptSubmit(毎ターン注入)→ Stop(事後検知でターン終了をブロック)の三層で守ります。 単層では抜けます。これは27日かけて学びました。

3. fail-open にします。 フック自身の障害や環境不備では静かに exit 0 で通します。フックの故障で正当な作業を止めません。 例外はブロック判定そのものだけです。

4. ブロックは3系統あります。 (a) JSON {"decision":"block","reason":…}。理由文をリッチに返せる、新しめの方式です (b) stderr バナー + exit 2。古参2本がこれです (c) 非ブロック(additionalContext 注入・警告のみ)

5. バイパスは必ず「明示トークン + 理由の記録」で作ります。 PROD_RELEASE_OK=1 / PROD_DEPLOY_WINDOW_OK=1 / STAGING_DEPLOY_OK=1 / WORKFLOW_VERIFIED=1 / SKIP_CHECK_BYPASS=1 / DOC_OK=1 / THEME_TOKEN_OK=1 / CONFIRM_ALERT_OK=1。 実装上はコマンド文字列中のトークンを grep で検知する方式で、環境変数の評価ではありません。

例外を許さないフックは現実に負けて無効化されます。 「黙って通れる抜け道」だけを塞ぐのが要点になります。

6. 状態ファイルでフック間を連携させます。 .claude/task-mode / .claude/browser-test-done / .claude/last-staging-deploy。すべて gitignored です。 UserPromptSubmit で判定した状態を、PreToolUse の別フックが執行する分業になっています。

7. 三層防御のパターンは2組あります。 出力規律(session-start → tool-first-reminder → stop-tool-markup-guard)と、 doc ドリフト防止(post-edit ナッジ → pre-commit 動的 discovery → pre-commit 静的 block/warn)。

1. 配線サマリ

イベント matcher / if hook
SessionStart (全セッション) session-start-output-discipline / session-start-linksee-setup / session-start-roles
UserPromptSubmit (全プロンプト) tool-first-reminder / user-prompt-task-mode
PreToolUse Bash + gh pr merge:* pre-merge-check
PreToolUse Bash + git commit:* pre-commit-test-skip-check / pre-commit-browser-test-check / pre-commit-theme-token-check / pre-commit-confirm-alert-check / pre-commit-doc-drift-check / pre-commit-doc-discovery
PreToolUse Bash + git checkout -b:* reset-browser-test-flag
PreToolUse Bash(全コマンド) pre-bash-prod-release-guard
PreToolUse Edit|Write|NotebookEdit pre-edit-task-mode-guard
PreToolUse Bash|staging-ssh exec pre-deploy-monitor-reminder / pre-bash-staging-deploy-guard
PreToolUse Linear MCP save_issue linear-auto-archive / pre-linear-done-check
PreToolUse linksee remember|update_memory linksee-content-guard
PostToolUse Bash + git commit:* post-commit-check
PostToolUse Edit|Write post-edit-doc-drift-nudge
Stop (全ターン終了) stop-verbal-action-mismatch / stop-tool-markup-guard

24本すべてが配線済み(未配線スクリプトなし)。PreToolUse の Bash 系は settings.json の if 絞り込み + スクリプト内自己判定の二重ゲート。

2. SessionStart 系(3本)— 毎セッション1回・情報注入のみ

session-start-output-discipline.sh

  • 目的: 「ツール呼び出しはメッセージ冒頭に単独で置く」出力規律を、最初のツール呼び出し前から文脈に在中させる(三層防御の baseline 層)
  • 動作機構: 静的バナー(厳守4か条)を stdout 出力のみ。常に exit 0
  • 誕生の経緯: 散文→ツール呼び出しの遷移で書式が崩れ、no-op 化して作業停止する事象の根本対策
  • 頻度: 毎セッション1回 / バイパス: なし
  • 移植のヒント: 「AI が繰り返す失敗」はプロンプト注入だけでは直りません。失敗のトリガー条件を特定して、その条件を規律で排除するバナーを毎セッション自動で供給します

session-start-linksee-setup.sh

  • 目的: プロジェクト記憶DBの git hook 自動取付(clone では復元されないため)+ 最近更新 entity 上位8件の提示で引き継ぎ参照漏れを防ぐ
  • 動作機構: .git/hooks/ のマーカー検査→欠落時 install-hook.sh 実行。旧版(WAL トリガなし)も再取付。sqlite3 で最近 entity 8件を stdout 注入。非破壊・常に exit 0
  • 誕生の経緯: SQLite WAL 滞留により手動 git add で記憶DBの更新を取りこぼした事故
  • 頻度: 毎セッション1回(インストールは欠落検知時のみ) / バイパス: なし
  • 移植のヒント: 「セットアップ手順書」は読まれません。SessionStart で冪等な自己修復スクリプトを回すほうが確実です

session-start-roles.sh

  • 目的: 常時ロードされない roles.md の最重要セクション「全ロール共通の規律」を毎セッション冒頭に注入する
  • 動作機構: awk で該当見出し区間のみ抽出して stdout 出力(文言の二重管理をしない DRY 設計)。ファイル不在時は静かに exit 0
  • 誕生の経緯:
  • 頻度: 毎セッション1回 / バイパス: なし
  • 移植のヒント: ルール文書から「注入すべき部分」を実行時に抽出する方式なら、正本を1箇所に保てます

3. UserPromptSubmit 系(2本)— 毎ターン

tool-first-reminder.sh

  • 目的: 出力規律 + 共通規律要約を毎ユーザーターンで additionalContext 注入(三層防御の per-turn 層)
  • 動作機構: 静的2行リマインドを stdout 出力。検知・ブロックなし
  • 誕生の経緯:
  • 頻度: 毎プロンプト / バイパス: なし
  • 移植のヒント: 長いセッションでは冒頭の指示が薄れます。「絶対に守らせたい2〜3行」だけを毎ターン再注入するのが、費用対効果が最大です

user-prompt-task-mode.sh

  • 目的: タスクモード契約の判定器。「確認して」(調査・計画のみ)と「実行して」(staging 検証まで一気通貫)をプロンプトのキーワードから判定し .claude/task-mode に永続化。編集ブロックの執行は pre-edit-task-mode-guard が担う分業
  • 動作機構: 正規表現2種(実行系優先)。該当なしは現状維持。investigate 中は毎ターンリマインド注入。<task-notification> 等のシステム通知由来の入力は判定対象外(サブエージェント成果物内の「実行して」で誤遷移した実害への対策)
  • 誕生の経緯:/ 内部事例
  • 頻度: 毎プロンプト(マーカー書込はキーワード検出時のみ)
  • バイパス: ユーザーの明示指示を引用した上でのモード切替のみ(AI の自己切替は禁止)
  • 移植のヒント: AI は人間が思っている以上におせっかいで、調査だけ依頼したつもりでも勝手に編集を始めます。言葉遣いからモードを機械判定して、編集ツール自体をブロックする2段構えが有効です

4. PreToolUse: git commit ガード系(6本)— コミット時のみ

pre-commit-test-skip-check.sh

  • 目的: E2E テストの skip 逃げ防止(「失敗テストはスキップでなく修正」の機械的執行)
  • 動作機構: staged な e2e spec に test.skip / test.only / .skip( があれば stderr バナー + exit 2 でブロック
  • 誕生の経緯: > 出典: COM 番号記載なし(行動原則の執行)
  • バイパス: SKIP_CHECK_BYPASS=1
  • 移植のヒント: AI は「とりあえず skip して緑にする」誘惑に弱いです。skip の混入をコミット境界で止めてください

pre-commit-browser-test-check.sh

  • 目的: feature/fix ブランチのコミット前に dev ブラウザテスト実施を強制
  • 動作機構: feature/*fix/* ブランチのみ、フラグファイル .claude/browser-test-done の存在を検査。不在なら JSON block。フラグはテスト実施後に touch で立て、ブランチ新規作成時に reset-browser-test-flag が消す
  • 誕生の経緯: > 出典: COM 番号記載なし(行動原則5の執行)
  • バイパス: フラグファイル方式(環境変数でなく「実施の証跡」を残す設計)
  • 移植のヒント: 「テストした」の自己申告を信じません。実施行為とフラグ操作をセットにして、ブランチ切替でリセットします

pre-commit-theme-token-check.sh

  • 目的: マルチテナント配色トークンのハードコード漏れをコミット境界で検知
  • 動作機構: staged な対象スコープの追加行のみを3種検査(ハードコードブランド色 / 単色ボタンclass / inline style={{)。検出で JSON block
  • 誕生の経緯: inline style の色指定が「CSS ファイル監査」と「inline→CSS 移行監査」の両方をすり抜けた事故
  • バイパス: THEME_TOKEN_OK=1(正当な固定色。理由をコミット/PR に記録)
  • 移植のヒント: grep 監査には盲点があります(inline style・props・SVG)。「監査で見つからなかった」ではなく「混入をコミット時に止める」側に倒してください

pre-commit-confirm-alert-check.sh

  • 目的: ネイティブ confirm() / alert() の混入防止(テーマ非追従・文言制御不能)
  • 動作機構: staged 追加行から window.confirm( / bare confirm('…') 等を検出(正規置換先の useConfirm は誤検知しない)。検出で JSON block
  • 誕生の経緯:
  • バイパス: CONFIRM_ALERT_OK=1
  • 移植のヒント: 「使ってはいけない API」は、コードレビューではなく commit 境界の grep で落とすのが安上がりです

pre-commit-doc-drift-check.sh

  • 目的: 「コードを変えたのに対応ドキュメントが同一コミットにない」ドリフトの静的マップ検知
  • 動作機構: manifest doc-ownership.tsv(codeGlob→docPath→risk)を評価。risk=block(金銭・DR・スキーマ・法務)は JSON block、risk=warn は警告のみ。docPath 不在は warn 降格。git -c core.quotepath=false 明示(日本語パスの8進エスケープで誤ブロックしたバグの修正)
  • 誕生の経緯:/ 内部事例/ 内部事例
  • バイパス: DOC_OK=1(挙動不変リファクタ等。理由を記録)
  • 移植のヒント: ドキュメントは「あとで更新」だと必ず腐ります。コードと doc の対応表を AI が読める形にして、同一コミット強制を境界に置いてください。全部を block にせず、致命領域だけ block・他は warn という濃淡が、運用を壊さないコツです

pre-commit-doc-discovery.sh

  • 目的: 静的マップに載っていない関連ドキュメントを動的検索で発見・提示する advisory(ブロックなし)
  • 動作機構: staged 差分からモジュール名・モデル名 + 日本語同義語マップで検索語を導出し、docs を横断 grep。言及2回以上の doc 上位8件を additionalContext 提示。常に exit 0
  • 誕生の経緯: D2
  • バイパス: なし(advisory)
  • 移植のヒント: 静的マップ(確実だが保守が要る)と動的検索(網羅的だがノイズが出る)を併用します。block と suggest を別の hook に分けると、片方の失敗が他方を壊しません

5. PreToolUse: その他 Bash 系(4本)

pre-merge-check.sh

  • 目的: マージ直前にフロー完了確認(tsc / test-unit / Docker / e2e / dev テスト / Linear コメント)を強制
  • 動作機構: gh pr merge を含み WORKFLOW_VERIFIED=1 を含まなければ stderr チェックリスト + exit 2
  • 誕生の経緯: > 出典: COM 番号記載なし(フロー遵守の執行)
  • バイパス: WORKFLOW_VERIFIED=1(全項目完了が前提。バッチ運用時は per-PR ゲート = tsc + 単体 + ビルド緑で許可)
  • 移植のヒント: 「マージ」は後戻りのコストが跳ね上がる境界です。チェックリストを門番にしてください

reset-browser-test-flag.sh

  • 目的: 新ブランチ作成時にブラウザテスト完了フラグを自動リセット(前ブランチの実績持ち越し防止)
  • 動作機構: git checkout -b 検知でフラグ rm -f + systemMessage 通知
  • 誕生の経緯: > 出典: COM 番号記載なし(browser-test-check とペア)
  • バイパス: 該当なし / 頻度: ブランチ作成時のみ
  • 移植のヒント: フラグ方式のガードには、必ず「リセット条件」を対で設計してください。リセットのないフラグは形骸化します

pre-bash-prod-release-guard.sh

  • 目的: 本番リリースの絶対ガード。ユーザー明示指示まで全ブロック + 指示があっても時間帯(JST AM2:00〜6:00)外はブロック
  • 動作機構: 全 Bash コマンドを5パターン照合(デプロイスクリプト prod 系 / CI dispatch / 本番ホスト IP への SSH + デプロイ複合。bare「deploy」は SSH ユーザー名に誤マッチするため使わず、read-only 監査 SSH は殺さない)。PROD_RELEASE_OK=1 付きは JST 時刻判定で窓内のみ通過
  • 誕生の経緯:+ 内部事例
  • 頻度: 全 Bash コマンドで評価 / バイパス: PROD_RELEASE_OK=1(ユーザー指示の引用が条件)+ 窓外はさらに PROD_DEPLOY_WINDOW_OK=1(緊急のみ・理由記録)
  • 移植のヒント: 「本番」だけは fail-open にしません。二段トークンと時間帯という物理条件で、AI の自己判断では絶対に通れない壁を作ります

pre-deploy-monitor-reminder.sh

  • 目的: デプロイ起動時に「完了監視は .done マーカーの短時間ポーリングで」とリマインド注入
  • 動作機構: デプロイスクリプト起動コマンドに一致した場合のみ additionalContext 出力。ブロックなし
  • 誕生の経緯: SSH idle 切断で完了通知が届かずデプロイ監視が消失した実害 > 出典: COM 番号なし(memory: feedback_deploy_monitoring_pattern)
  • バイパス: なし
  • 移植のヒント: 「正しい手順を思い出させる」だけの軽量な hook にも価値があります。ブロックするほどではないけれど毎回間違えやすい操作に向いています

pre-bash-staging-deploy-guard.sh

  • 目的: staging デプロイの頻度ガード(セッション原則1回・バッチ一括)。CI 無料枠の保護
  • 動作機構: staging デプロイ系コマンドを検知し、マーカー .claude/last-staging-deploy(epoch 秒)と比較。前回から4時間未満なら JSON block。通過時にマーカー更新(失敗デプロイも1回と数える保守的挙動)
  • 誕生の経緯: 1日10回の staging デプロイで翌日 GitHub Actions 無料枠 2,000分/月を使い切り、CI 全停止
  • バイパス: STAGING_DEPLOY_OK=1(例外条件 = DB マイグレーション / 金銭 BE / 緊急修正、またはバッチ一括。理由を記録)
  • 移植のヒント: AI は教えなければ、他サービスの従量課金・無料枠の概念を考慮しません。コストが漏れる操作には、時間ベースのレートリミットを hook で付けてください

6. PreToolUse: 編集・MCP 系(4本)

pre-edit-task-mode-guard.sh

  • 目的: 確認モード中のリポジトリ内ファイル編集をブロック(タスクモード契約の執行側)
  • 動作機構: .claude/task-modeinvestigate かつ24時間以内なら、リポジトリ内への Edit/Write/NotebookEdit を JSON block。例外: リポジトリ外・prompts 保存・マーカー自身
  • 誕生の経緯:
  • バイパス: ユーザー明示指示の引用 + モード切替のみ
  • 移植のヒント: 判定(プロンプト解析)と執行(ツールブロック)を別の hook に分けると、それぞれを単純に保てます

linear-auto-archive.sh

  • 目的: イシュートラッカー無料枠の active issue 上限(250件)を先回りで回避。上限近傍で古い closed issue を自動アーカイブ
  • 動作機構: issue 新規作成時のみ GraphQL で active 数をカウント。閾値245到達で Done/Canceled/Duplicate の最古100件(直近7日は除外)をアーカイブ。旧実装は PostToolUse で「上限エラー後」に発火せず機能しなかった→ PreToolUse の先回りに変更。API 鍵は secrets ファイル分離。常に exit 0(失敗しても issue 作成を妨げない)
  • 誕生の経緯: > 出典: 鍵管理は 内部事例。機構自体の起票番号は記載なし
  • バイパス: なし(非ブロック) / 頻度: issue 新規作成時のみ実クエリ
  • 移植のヒント: 上限系の自動対処は「エラー後のリカバリ」ではなく「先回り」で設計してください。エラー時には、もう hook が発火できないことがあります

pre-linear-done-check.sh

  • 目的: イシューを Done にする直前に完了チェックリスト(テスト・ブラウザ検証・コメント登録)を想起させる
  • 動作機構: state=Done への変更時のみ stderr バナー。exit 0 で警告のみ
  • 誕生の経緯: > 出典: COM 番号記載なし(早期 Done 事故群の補助策)
  • バイパス: なし(非ブロック)
  • 移植のヒント: 「Done」はごまかしが集中する境界です。ブロックできない性質の確認(目視の有無など)は、せめて直前のチェックリスト提示で補ってください

linksee-content-guard.sh

  • 目的: 記憶DBへの長文書き込みを警告し、DB 肥大による recall 機能停止の再発を防ぐ
  • 動作機構: 書込内容が1500字超で additionalContext 警告。ブロックはしない(正当な長文を妨げない)
  • 誕生の経緯: 長文蓄積で recall が 151KB 超過エラーになり記憶機構が機能停止
  • バイパス: なし(警告のみ)
  • 移植のヒント: AI の記憶は書きすぎで壊れます。「実装詳細はイシューコメント、記憶は要点のみ」の役割分担を warning で守らせてください

7. PostToolUse 系(2本)— 事後・非ブロック

post-commit-check.sh

  • 目的: コミット直後に次工程(dev ブラウザテスト→staging→結果コメント)をリマインド
  • 動作機構: 静的バナー stderr 出力のみ(PostToolUse はブロック不可)
  • 誕生の経緯: > 出典: COM 番号記載なし
  • 移植のヒント: 「コミットで満足して検証を忘れる」ことへの対策です。次の一歩を毎回言わせます

post-edit-doc-drift-nudge.sh

  • 目的: 高リスク領域の編集直後に対応ドキュメント更新を促す早期ナッジ(主検知は pre-commit 側)
  • 動作機構: 編集パスを doc-ownership.tsv の block 行のみと照合(毎編集で鳴ると煩いため warn 行は対象外)。一致時のみ additionalContext 提示
  • 誕生の経緯:
  • 移植のヒント: 同じ検査でも「編集直後の軽いナッジ」と「コミット時の強制」で二度伝えると、修正コストが最小の時点で気づけます。ナッジは高リスクのみに絞って、警告疲れを防いでください

8. Stop 系(2本)— 毎ターン終了時

stop-verbal-action-mismatch.sh

  • 目的: 言行一致の執行。「続けます」等の未来宣言をしたのに tool 呼び出し0件でターンを閉じるのを防ぐ
  • 動作機構: 最終 assistant メッセージを解析。tool_use ありはスルー / 疑問符・入力待ち文末はスルー / 宣言フレーズ16種を検出したら JSON block で停止を差し戻し。stop_hook_active=true 時はスルー(無限ループ防止の循環ブレーカ)
  • 誕生の経緯:
  • バイパス: なし(質問で終えるか、宣言どおり tool を呼ぶのが正規の通過)
  • 移植のヒント: AI は「やります」と言って止まります。Stop hook はターン終了自体を差し戻せる最後の砦です。ループ防止の circuit breaker を必ず入れてください

stop-tool-markup-guard.sh

  • 目的: ツール呼び出し書式ミス(未パース markup の no-op 化)の事後検知と復帰指示(三層防御の post-turn 層)
  • 動作機構: 最終メッセージに正常 tool_use があればスルー。テキスト中の失敗シグネチャ(リテラルタグ・先頭混入語+開始タグ)を検出したら JSON block で「前置きなし・正書式で即再送」を指示。ブロック理由文自体にタグ形を含めない(自己再発火防止)。循環ブレーカあり
  • 誕生の経緯: 書式ミスによりイシューコメント登録が no-op 化し作業停止
  • バイパス: なし(正書式で再送すれば通過)
  • 移植のヒント: 検知パターンを設計するときは、「検知メッセージ自体が次の検知に引っかからないか」まで考えてください

9. 頻度マトリクス(運用コスト感覚)

頻度帯 hooks
毎セッション1回 SessionStart 3本
毎プロンプト UserPromptSubmit 2本
毎ターン終了 Stop 2本
全 Bash / 全編集で評価(発動は条件一致時のみ) prod-release-guard / staging-deploy-guard / deploy-monitor-reminder / edit-task-mode-guard / doc-drift-nudge
コミット時のみ git commit 系6本 + post-commit-check
マージ・ブランチ作成・イシュー保存・記憶書込時のみ pre-merge-check / reset-browser-test-flag / linear 2本 / linksee-content-guard

10. フックの書き方

ここまで24本の「何をしているか」を並べてきました。最後に「どう書くか」を置いておきます。 特別な技術は要らない、というのを見てもらいたいからです。

配線する(settings.json)

フック本体はただのスクリプトで、いつ呼ぶかは settings.json が決めます。 matcher でツールを絞り、Bash 系はさらにコマンド文字列で絞り込みます。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "if": "git commit",
        "hooks": [
          { "type": "command", "command": ".claude/hooks/pre-commit-browser-test-check.sh" }
        ]
      },
      {
        "matcher": "Edit|Write|NotebookEdit",
        "hooks": [
          { "type": "command", "command": ".claude/hooks/pre-edit-task-mode-guard.sh" }
        ]
      }
    ],
    "Stop": [
      { "hooks": [ { "type": "command", "command": ".claude/hooks/stop-verbal-action-mismatch.sh" } ] }
    ]
  }
}

if の絞り込みだけに頼らず、スクリプト側でも自己判定する二重ゲートにしてあります。 配線を書き換えたときに、フックが黙って全コマンドで走り出す事故を避けるためです。

いちばん短い形(stderr + exit 2)

止めるだけならこれで足ります。フックは標準入力に JSON でツール呼び出しの情報を受け取ります。

#!/usr/bin/env bash
# pre-merge-check.sh — マージ前にフロー完了確認を強制する
set -uo pipefail                      # -e は付けない(途中の失敗で沈黙終了させない)

input=$(cat)
cmd=$(printf '%s' "$input" | jq -r '.tool_input.command // ""' 2>/dev/null) || exit 0
[ -n "$cmd" ] || exit 0                                   # 読めなければ通す(fail-open)

case "$cmd" in *"gh pr merge"*) ;; *) exit 0 ;; esac      # 対象外は通す
case "$cmd" in *"WORKFLOW_VERIFIED=1"*) exit 0 ;; esac    # 明示バイパス

cat >&2 <<'MSG'
マージ前チェックが未完了です。次を確認してから WORKFLOW_VERIFIED=1 を付けて再実行してください。
  [ ] 型チェック エラーゼロ   [ ] 単体テスト全 PASS   [ ] コンテナ全 healthy
  [ ] dev ブラウザ検証        [ ] イシューへ結果コメント
MSG
exit 2

要点は3つです。

exit 2 が「止める」の合図になります。exit 0 は通します。 入力が読めない・jq が無いといったフック側の不調では必ず通します(fail-open)。 そしてバイパスは、コマンド文字列にトークンが書かれていることを見ます。 環境変数を評価すると、シェルの外から黙って渡せてしまって痕跡が残りません。

理由文を返す形(JSON block)

止める理由を長く返したいときは、stdout に JSON を書きます。

#!/usr/bin/env bash
# pre-commit-browser-test-check.sh — dev ブラウザテスト未実施のコミットを止める
set -uo pipefail

branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || exit 0
case "$branch" in feature/*|fix/*) ;; *) exit 0 ;; esac
[ -f .claude/browser-test-done ] && exit 0                # 実施の証跡があれば通す

jq -n --arg r 'このブランチで dev ブラウザテストの証跡がありません。
実施したら `touch .claude/browser-test-done` を実行してからコミットしてください。
(フラグはブランチ新規作成時に自動で消えます)' \
  '{decision:"block", reason:$r}'
exit 0

バイパスを環境変数ではなくフラグファイルにしているのがポイントになります。 「テストした」という自己申告ではなく、実施行為とフラグ操作をセットにします。 そして git checkout -b を検知する別のフックがフラグを消します。

リセットのないフラグは必ず形骸化します。 ガードとリセットは対で設計してください。

止めない形(additionalContext 注入)

止めるほどではないが毎回間違える操作には、正しい手順を差し込むだけのフックが効きます。

#!/usr/bin/env bash
# pre-deploy-monitor-reminder.sh — デプロイ起動時に監視の作法を思い出させる
set -uo pipefail
cmd=$(cat | jq -r '.tool_input.command // ""' 2>/dev/null) || exit 0
case "$cmd" in *deploy-staging.sh*|*deploy-prod.sh*) ;; *) exit 0 ;; esac

jq -n --arg c '完了監視は SSH を張りっぱなしにせず、`.done` マーカーを
1〜2秒の短時間接続で 60〜90 秒ごとにポーリングして確認すること。' \
  '{hookSpecificOutput:{hookEventName:"PreToolUse", additionalContext:$c}}'
exit 0

SessionStart や UserPromptSubmit のフックは、これの stdout 出力版だと思えば大丈夫です。 バナーを echo するだけで文脈に載ります。

ターン終了を差し戻す形(Stop)

Stop フックはターンの終了そのものを止められます。最後の砦になります。 ただし循環ブレーカが必須です。差し戻しの文言がまた検知に引っかかると、無限に回ります。

#!/usr/bin/env bash
# stop-verbal-action-mismatch.sh — 「やります」と言って止まるのを差し戻す
set -uo pipefail
input=$(cat)

# 差し戻しで再入した回は必ず通す(無限ループ防止の循環ブレーカ)
[ "$(printf '%s' "$input" | jq -r '.stop_hook_active // false')" = "true" ] && exit 0

last=$(printf '%s' "$input" | jq -r '.last_assistant_message // ""')
printf '%s' "$last" | jq -e '.tool_use' >/dev/null 2>&1 && exit 0   # 実際に呼んでいれば通す
case "$last" in *"?"|*"?"|*"ください") exit 0 ;; esac               # 質問で終わるのは正常

case "$last" in
  *"続けます"*|*"次に"*|*"これから"*|*"実行します"*)
    jq -n '{decision:"block", reason:"宣言した作業が実行されていません。前置きなしで、宣言どおりツールを呼んでください。"}'
    ;;
esac
exit 0

書式ミス検知のフック(stop-tool-markup-guard)では、もう一段の注意が要りました。 ブロック理由文の中に、検知対象のタグ形を書きません。 書くと自分の出力を自分で検知して止まらなくなります。

検知パターンを設計するときは、「検知メッセージ自体が次の検知に引っかからないか」まで考えてください。

AI に書かせるときの依頼文

このカタログの24本は、ほぼ全部 AI が書きました。依頼に入れるのは3つだけで足ります。

  1. 検知する条件(どのコマンド・どのファイル・どの状態か)
  2. 止めるのか、警告するのか
  3. バイパスのトークン名と、理由の記録先

そして受け取ったら、フェイルオープンがどこに入ったかを指させてください。 そこを説明できないスクリプトは、まだ本番に配線しないほうがいいです。