メインコンテンツへスキップ
見出し画像

Claude CodeのHooks入門|「毎回お願いしていたこと」を、必ず走る処理にする

    Claude Codeを使っていると、「お願い」が定型化してきます。

    「編集したらフォーマッタをかけて」「.envは絶対に触らないで」「終わったら教えて」

    CLAUDE.mdに書いても、指示が常に守られるとは限りません。AIに指示している限り、それは「守ってくれたらいい」であって、「そういう処理になっている」ではないからです。

    このすり抜けを潰すのがHooksです。

    Hooksは、Claude Codeの動作の特定のタイミングで自動的に実行される処理。シェルコマンドが基本ですが、それ以外の種類も選べます。

    本記事はデスクトップアプリ版を前提に、Hooksの仕組み、設定ファイルの書き方、そのままコピーして使える実用レシピ4つ、そして確実にハマるポイントまでまとめます。


    Hooksとは

    Hooksは、Claude Codeのライフサイクル上の特定の地点で自動実行されるユーザー定義の処理です。

    画像
    参考:https://code.claude.com/docs/ja/hooks

    イベントが発火し、マッチャーなどの条件を満たしたときに走ります。 公式ドキュメントの言い方を借りれば、決定論的な制御(deterministic control)です。

    LLMが「実行しようと判断する」ことに頼らず、Claude Code側の処理として実行させる仕組みです。

    実行するものはシェルコマンドだけではありません。HTTPエンドポイントへのPOST、接続済みMCPサーバーのツール呼び出し、そしてClaudeモデルによる判定(プロンプト/エージェント)も選べます。

    画像
    各種違い

    判断の目安は「守られなかったときに困るもの」はHooksへ、「毎回説明するのが面倒なもの」はスキルへ、「前提として知っておいてほしいもの」はCLAUDE.mdに記載です。

    なおスキルについては下記の記事で解説しているので、ご興味ある方はぜひご覧ください。

    Hooksは3つの部品でできている

    Hooksの設定は、常にこの3層で書きます。

    1. イベント:いつ動かすか。PostToolUse(ツール実行後)、Notification(入力待ちの通知時)など

    2. マッチャー:どの操作のときだけ動かすか。Edit|Writeのようにツール名を|で並べて指定する。大文字小文字は区別されます

    3. ハンドラー:何を実行するか。"type": "command"ならシェルコマンド

    イベントは現在30種類以上ありますが、最初に覚えるのは5つで足りると思います。

    画像
    Hooksイベント

    ツールの実行そのものを事前に止められる代表的なイベントがPreToolUseです。一方PostToolUseはツールが既に実行された後に走るので、取り消しはできません。


    Hooksのデスクトップ版は設定ファイルを直接編集する

    Hooksは設定ファイルのhooksブロックに書きます。基本となる置き場所は次の3つで、どこに書くかで有効範囲が変わります。

    画像

    このほか、組織で配布する管理ポリシー設定、プラグイン、スキルやサブエージェントのfrontmatterにもHooksを定義できます。まずは上の3つを押さえておきましょう。

    なおClaude Codeの運用の型について、下記の記事で解説しているのでご興味ある方はぜひご覧ください。


    レシピ①:入力待ちになったら通知する

    Hooksとして効果が分かりやすく、比較的リスクが低いのがこれです。

    Claudeが許可を求めて止まったとき、デスクトップ通知を出します。長い作業を任せて別のことをしていても、手が止まったまま放置されている状態に気づけます。

    ~/.claude/settings.jsonに追加します(macOSの例)。マッチャーにpermission_promptを指定して、許可待ちのときだけ通知するようにしています。

    {
      "hooks": {
        "Notification": [
          {
            "matcher": "permission_prompt",
            "hooks": [
              {
                "type": "command",
                "command": "osascript -e 'display notification \"Claude Codeが入力を待っています\" with title \"Claude Code\"'"
              }
            ]
          }
        ]
      }
    }

    マッチャーを空文字にするとすべての種類の通知で発火します(許可待ちのほか、アイドル、認証、MCP関連など)。

    また、この通知は許可待ちがしばらく続いてから出ます。「止まった瞬間に鳴る」ものではないです。


    レシピ②:編集後に自動でフォーマットをかける

    Claudeがファイルを編集するたび、Prettierを走らせます。プロジェクトの.claude/settings.jsonに追加します。

    {
      "hooks": {
        "PostToolUse": [
          {
            "matcher": "Edit|Write",
            "hooks": [
              {
                "type": "command",
                "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
              }
            ]
          }
        ]
      }
    }

    やっていることは単純で、Hooksの標準入力に流れてくるJSONからjqで編集対象のファイルパスを取り出し、Prettierに渡しているだけです。Pythonならblack、Goならgofmtと、コマンドを差し替えるだけで応用できます。

    これは公式ガイドにも載っている書き方ですが、パスに空白が含まれるとxargsが分割してしまいます。日本語のフォルダ名やスペース入りのパスを扱うなら、スクリプトに切り出して変数をクオートするほうが安全です。

    #!/bin/bash
    FILE=$(jq -r '.tool_input.file_path // empty')
    [ -n "$FILE" ] && npx prettier --write "$FILE"

    jqが入っていない場合はbrew install jqで入れてください。JSONさえ読めればいいので、PythonやNode.jsで書いても構いません。

    成功しても会話には何も表示されません。 動いたかどうかは、ファイルが実際に整形されているかで確認します。


    レシピ③:触らせたくないファイルを守る

    ここがHooksの本領です。EditやWriteツール経由で.envやロックファイル、.git/配下を書き換えようとした場合に、実行前に止めるHooksを作ります。

    まず.claude/hooks/protect-files.shを作ります。

    #!/bin/bash
    INPUT=$(cat)
    FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
    
    PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/")
    
    for pattern in "${PROTECTED_PATTERNS[@]}"; do
      if [[ "$FILE_PATH" == *"$pattern"* ]]; then
        echo "ブロック: $FILE_PATH は保護対象($pattern)です" >&2
        exit 2
      fi
    done
    
    exit 0

    実行権限を付けます。これを忘れると動きません。

    chmod +x .claude/hooks/protect-files.sh

    .claude/settings.jsonに登録します。

    {
      "hooks": {
        "PreToolUse": [
          {
            "matcher": "Edit|Write",
            "hooks": [
              {
                "type": "command",
                "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
              }
            ]
          }
        ]
      }
    }

    ここだけは間違えないでほしい2点

    1. 入力は標準入力のJSONで来る。環境変数ではない。

    Hooksが受け取るのは、session_id・cwd・tool_name・tool_inputなどを含むJSONです。スクリプトの冒頭で`$(cat)`して読むのが基本形になります。

    2. ブロックする終了コードは2。1ではない。

    終了コードで止めるならexit 2です。これだけが「この操作を止める」という意味を持ち、標準エラー出力の内容がClaudeにフィードバックされます。

    上の例なら「.envは保護対象です」がClaudeに伝わり、Claudeは別の方法を考えます。exit 1など他の非ゼロは単なるエラー扱いで、処理は止まりません。

    権限設定との関係

    PreToolUseHooksはあらゆる権限モードのチェックより前に発火します。仕様上、denyを返すHooksは権限をすべて自動許可するモードでも効きます。

    利便性のために権限を緩めても、絶対に触らせない線だけはHooksで守れるわけです。

    一方で、設定側の拒否ルールをHooksが上書きして緩めることはできません。Hooksがallowを返しても、denyルールは覆せません。

    注意したいのは、「Hooksは緩める方向には一切使えない」わけではない点です。PreToolUseでallowを返したり、PermissionRequestHooksで許可を代理させたりすれば、毎回出る許可プロンプトを自動承認させることはできます。


    レシピ④:会話が要約されても前提を忘れさせない

    会話が長くなるとコンテキストが要約され、序盤の前提が落ちることがあります。SessionStartイベントにcompactマッチャーを付けると、要約のたびに前提を再注入できます。

    {
      "hooks": {
        "SessionStart": [
          {
            "matcher": "compact",
            "hooks": [
              {
                "type": "command",
                "command": "echo '再確認:npmではなくBunを使う。コミット前にbun testを実行する。'"
              }
            ]
          }
        ]
      }
    }

    標準出力に書いたテキストが、そのままClaudeのコンテキストに追加されます。 echoの代わりにgit log --oneline -5を実行して直近のコミットを流し込む、といった動的な使い方もできます。

    なお毎回のセッション開始時に必ず読ませたい前提は、HooksではなくCLAUDE.mdに書くのが良いです。Hooksは動的情報や会話固有情報を再注入する用途に向きます。


    「自社だけでClaude Codeをうまく活用できるか不安」「どこから手をつければいいかわからない」という方がいらっしゃれば、私たちWEELにご相談ください。

    Claude Codeの導入支援からプロンプト設計、社内展開のサポートまで、実務に即した形でしっかり伴走します。


    判断が要る処理は、コマンド以外のHooksにする

    シェルコマンドで機械的に判定できないものもあります。

    「本当に全部終わったか」「この変更は方針に沿っているか」

    こうした判断には、command以外のハンドラーが用意されています。

    • "type": "prompt":Hooksの入力をClaudeモデルに渡して判定させる。1回のやり取りで完結

    • "type": "agent":サブエージェントを立ててファイルを読んだりコマンドを走らせたりして検証する

    たとえば「終わったつもりで止まる」のを防ぐStopHooksはこう書けます。

    {
      "hooks": {
        "Stop": [
          {
            "hooks": [
              {
                "type": "prompt",
                "prompt": "依頼されたタスクが全て完了しているか確認する。未完了なら {\"ok\": false, \"reason\": \"残っている作業\"} を返す。 $ARGUMENTS"
              }
            ]
          }
        ]
      }
    }

    ok: falseが返るとClaudeはreasonを次の指示として作業を続けます。末尾の`$ARGUMENTS`はHooksの入力データが差し込まれる場所です。

    入力データだけで判断できるならprompt、コードの実態を確認しないと判断できないならagent、が使い分けの目安です。


    ハマりどころ、先に全部書いておきます

    私が実際に踏んだものと、公式が挙げているものをまとめます。Hooksが動かないときの原因はだいたいこの中にあります。

    動かない系

    • 実行権限がない:chmod +xを忘れている

    • パスが不安定:相対パスは実行時のカレントディレクトリ次第で解決先が変わります。`$CLAUDE_PROJECT_DIR`を使うか絶対パスにしてください

    • jq: command not found:jqが未インストール

    • JSONが壊れている:末尾カンマやコメントは許容されません

    • マッチャーの表記ミス:ツール名は大文字小文字を区別します

    動いているのに効かない系

    • exit 1でブロックしたつもり:止まるのはexit 2だけ

    • HooksのJSON出力が無視される:シェルの設定ファイルに無条件のechoがあると、その出力がHooksの標準出力の先頭に混ざり、JSONとして解釈されなくなります。

    設計の落とし穴

    • PostToolUseでは取り消せない:止めたいならPreToolUse

    • StopHooksの無限ループ:StopHooksが連続でブロックすると強制的に打ち切られます。入力JSONのstop_hook_activeがtrueなら早期にexit 0する処理を入れておきます

    • Stopは完了時だけではない:Claudeが応答を終えるたびに発火します

    • Hooksは並列で走る:同じイベントの複数Hooksは同時実行され、denyが出ても他のHooksは止まりません


    まず通知から、次にフォーマット、そして保護

    Hooksは「毎回お願いしていたこと」を、LLMの気まぐれな判断ではなく、Claude Codeのイベント処理として実行させる仕組みです。

    CLAUDE.mdやスキルがClaudeの判断を通るのに対し、コマンド系のHooksは判断を通らない、この違いが、そのまま使い分けの基準になります。

    導入は次の順序をおすすめします。

    1. Notificationで通知:リスクゼロ。Hooksが動く感覚を掴む

    2. PostToolUseで自動フォーマット:毎回の指摘が消える

    3. PreToolUseで保護:触られたくないファイルを止める

    4. SessionStartで前提の再注入:長い作業で効いてくる

    まずは通知の3行から。「言わなくても勝手に走る」状態を一度作ると、Claude Codeに任せられる作業の範囲が明確に広がります。


    WEELでは
    生成AIやAIエージェントに関する無料オンラインセミナーも開催中です!
    この機会にぜひお気軽にご参加いただけますと幸いです!
    ✅ セミナーに参加する

    生成AIの最新ニュース・事例・イベント情報をいち早く知りたい方は、ぜひWEELの無料メルマガをご購読ください!
    ✉️ メルマガに登録する

    💡 AI活用、どこから始めればいいかお悩みの方へ
    「Claude Codeの使い方を知りたい」
    「業務効率化をして、仕事を楽にしたい!」

    そんな方向けに、無料の個別相談をやってます!
    現場での事例も交えて、わかりやすくお話しします。
    ✨今すぐAIの専門家と話す✨

     
     
    「日本一透明性の高いAIプロフェッショナル集団」を目指す株式会社WEELです。 社内外のセミナーレポやよくいただくご質問などをお届けします! ビジネスユーザーが見るべき記事や最新情報については こちらのサイトからご覧ください。(https://weel.co.jp/)

    あなたへのおすすめ