a/ analytics note .jp

TECH · field log

Claude Code カスタムフックで AI エージェントの暴走を防ぐ ─ PreToolUse / PostToolUse 設計パターン

Claude Code の hooks.json を使って AI エージェントの危険な操作をブロックし、コミット規律やコード品質を自動で強制する設計パターンを解説します。

· 6 min read · #Claude Code / #AI / #開発自動化 / #セキュリティ / #フック / #Git · AI-assisted · reviewed Share on X はてブ Zennにクロスポスト

はじめに

Claude Code などの AI コーディングエージェントは生産性を大幅に向上させますが、誤った操作で未コミットの変更を吹き飛ばしたり、直接 main ブランチへ push してしまったりするリスクも伴います。

Claude Code には hooks.jsonカスタムフックを定義する機能があり、ツール実行前後に任意のスクリプトを割り込ませることができます。このフック機構を活用すると、AI エージェントの行動にガードレールを設けることができます。

本記事では、実際のプロジェクトで運用されているフック設計パターンを解説します。すべての実装例はそのまま自分のプロジェクトに適用できます。

Claude Code カスタムフックの設定方法と基本構造

Claude Code は .claude/hooks.json(または settings.json 内の hooks)でフックを定義します。

{
  "$schema": "https://json.schemastore.org/claude-code-hooks.json",
  "hooks": {
    "PreToolUse": [
      {
        "id": "block-dangerous-operation",
        "description": "危険な操作をブロックする",
        "matcher": "Bash",
        "command": "node .claude/scripts/block-dangerous-operation.js \"$TOOL_INPUT\""
      }
    ],
    "PostToolUse": [
      {
        "id": "auto-format",
        "description": "編集後に自動フォーマット",
        "matcher": "Write|Edit",
        "command": "node .claude/scripts/auto-format.js \"$TOOL_INPUT\""
      }
    ]
  }
}

フックが exit code 2 を返すとツール実行がブロックされます。exit code 0 は許可、それ以外は警告扱いです。

フックイベント一覧

イベントタイミング
UserPromptSubmitユーザーがプロンプトを送信する直前
PreToolUseツールを実行する直前
PostToolUseツール実行後
PreCompactコンテキスト圧縮前
SessionStartセッション開始時

Claude Code フックの実践パターン:破壊的操作の防止

実装例1: 破壊的 git 操作のブロック(pre-destructive-git)

最も重要なガードレールがこれです。git checkout --git cleangit reset --hard は未コミットの変更を消去する操作です。AI エージェントがこれらを実行すると、手元の作業が失われることがあります。

// .claude/scripts/pre-destructive-git.js

const DESTRUCTIVE_PATTERNS = [
  /\bgit\s+checkout\s+--\s/,
  /\bgit\s+checkout\s+--$/,
  /\bgit\s+clean\b/,
  /\bgit\s+reset\s+--hard\b/,
];

const input = process.argv[2] || "";
let command = "";
try {
  command = JSON.parse(input).command || "";
} catch {
  command = input;
}

const isDestructive = DESTRUCTIVE_PATTERNS.some((p) => p.test(command));
if (!isDestructive) process.exit(0);

// 未コミット変更を確認
const status = execSync("git status --porcelain").trim();
if (!status) process.exit(0); // クリーンなら許可

// 未コミット変更があればブロック
process.stderr.write(
  `❌ ブロック: 未コミット変更が ${count} 件あります\n` +
  `先に git add -A && git commit -m "chore: WIP save" してください\n`
);
process.exit(2); // exit 2 でブロック

このフックにより、未コミット変更がある状態では破壊的操作が自動的にブロックされます。AI エージェントが誤ってリセットしようとしても、コミットが先に強制されます。

実装例2: コミットリマインダー(commit-reminder)

ファイルを大量に変更しても AI エージェントはコミットを後回しにしがちです。PostToolUse フックで変更ファイル数を監視し、閾値を超えたらリマインドします。

// .claude/scripts/commit-reminder.js

const THRESHOLD = parseInt(process.env.COMMIT_REMINDER_THRESHOLD || "5", 10);

const status = execSync("git status --porcelain").trim();
if (!status) process.exit(0);

const count = status.split("\n").filter(Boolean).length;
if (count < THRESHOLD) process.exit(0);

// しきい値に達したときのみリマインド(毎回は出さない)
process.stdout.write(
  `💾 コミット推奨: 未コミット変更が ${count} 件あります\n` +
  `論理的な区切りでコミットしてください。\n`
);

process.exit(0); // 警告のみ、ブロックはしない

ポイントはブロックではなく警告のみにしていること。強制力は pre-destructive-git が担い、リマインダーは気づきを促す役割に限定しています。

実装例3: main/develop への直接 push ブロック

# .claude/hooks/pretool-guard.sh(Shell 版の実装例)

# main/develop への直接 push をブロック
if echo "${command_text}" | grep -Eq 'git push.* origin (main|develop)'; then
  echo "[BLOCK] main/develop への直接 push は禁止です。" >&2
  exit 2
fi

# --no-verify によるフックバイパスをブロック
if echo "${command_text}" | grep -Eq 'git (commit|push).*--no-verify'; then
  echo "[BLOCK] --no-verify は禁止です。" >&2
  exit 2
fi

このガードにより、AI エージェントが feature/xxx ブランチで作業しているつもりで main に直接 push してしまう事故を防ぎます。

実装例4: プロンプト内の秘密情報検出(detect-secrets-in-prompt)

UserPromptSubmit フックを使うと、ユーザーがプロンプトに API キーや秘密情報を貼り付けた場合に警告できます。

{
  "UserPromptSubmit": [
    {
      "id": "detect-secrets-in-prompt",
      "description": "ユーザープロンプト内の機密情報パターンを検出して警告する",
      "command": "node .claude/scripts/detect-secrets-in-prompt.js"
    }
  ]
}

AI にシステムプロンプトや会話履歴として秘密情報が流れるリスクを軽減できます。

実装例5: CI 自動確認(check-ci)

PostToolUsegit push または gh pr create の後に CI 結果を自動確認するフックです。

{
  "PostToolUse": [
    {
      "id": "check-ci",
      "description": "git push / gh pr create 後に CI チェック結果を報告する",
      "matcher": "Bash",
      "command": "bash .claude/scripts/check-ci.sh",
      "timeout": 190
    }
  ]
}

AI エージェントが PR を作成したあと、人間が確認しなくても CI の pass/fail が自動的に会話に返ってきます。

フック設計の原則

実際に運用して分かったフック設計の原則をまとめます。

ブロックと警告を使い分ける

操作リスク対応exit code
復元不可能な破壊操作ブロック2
好ましくないが回復可能警告0
検知のみ(情報提供)通知0

フック有効/無効の切り替え

フックを環境によって無効化できるように hook-gate.js パターンを使います。

// .claude/scripts/hook-gate.js
function shouldRun(hookId) {
  const disabled = process.env.CLAUDE_DISABLE_HOOKS || "";
  return !disabled.split(",").includes(hookId);
}
module.exports = { shouldRun };

各スクリプトの冒頭で確認します:

const { shouldRun } = require("./hook-gate");
if (!shouldRun("commit-reminder")) process.exit(0);

これにより、特定の環境やデバッグ時だけフックを外せます。

コミット規律との組み合わせ

フックはルール単体では機能しません。コミット規律のルールと組み合わせることで真価を発揮します。

# .claude/rules/commit-discipline.md

## コミットタイミング
- 機能の論理的な区切り
- テストを追加・修正した後
- 設定ファイルを変更した後
- 大きな作業の前(リファクタ・削除の前に現状保存)

## ハーネスによる強制
pre-destructive-git フックが以下の操作の前に未コミット変更を検出:
- git checkout -- <file>
- git clean -fd
- git reset --hard HEAD

ルールでコミットタイミングを定め、フックでルール遵守を技術的に強制する ── この二段構えが重要です。

まとめ

Claude Code のカスタムフックを活用することで、AI エージェントの行動に対して以下のガードレールを設けられます。

  • 未コミット変更を保護: 破壊的 git 操作の前に強制コミット
  • ブランチ保護: main/develop への直接 push をブロック
  • セキュリティ: —no-verify バイパス禁止、プロンプト内秘密情報検出
  • 品質担保: 編集後の自動フォーマット、CI 自動確認

フックは「うっかり防止」のための仕組みです。AI エージェントを完全に縛るためではなく、人間でも AI でも踏み外しやすい操作だけをガードする設計が運用しやすくなります。

関連記事

F/ この記事の設計を反映しているプロダクト: FlowAgent

see →
an

analytics note — editor

AI とデータ分析の実装ログを毎週編集。設計判断と運用のつまずきを、再現できる形で残すことを大切にしています。