実務メモ

Claude Codeのhooksが動かないときの確認順 ― 自社で踏んだ4つの穴

Claude Code の hooks は、操作の直前や直後に、自分で書いたコマンドを走らせる仕組みです。危ない操作を止める最後の砦として使えます。

困るのは、動いていなくても何も表示されないことです。自社では、安全装置として作ったフック4本が、しばらく一度も発火していませんでした。そのとき見つけた穴を、確認する順に書きます。

先に1回、わざと踏ませる

原因を探す前に、本当に動いていないのかを確かめます。止めたい操作を、ダミーの入力で1回だけやらせてみてください。 止まれば、配線は生きています。

自社で気づいたきっかけも、この確かめ方です。大量の変更をまとめて記録する操作を止めるフックがあったのに、 上限を超える量が、そのまま通ってしまいました。同じ入力をフックのスクリプトへ手で渡すと、ちゃんと止まりました。 中身は正しく、呼ばれていなかったことになります。

まだフックが無い段階なら、配線だけを確かめる短いフックを1本置いて試すと切り分けが早くなります。 たとえば、決めた文字列を含むコマンドだけを止めるフックです。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "python C:/tools/hooks/guard.py" }
        ]
      }
    ]
  }
}
# guard.py ― 標準入力で受け取った JSON からコマンドを取り出して判定する
import json, sys
data = json.loads(sys.stdin.buffer.read().decode("utf-8"))
cmd = data.get("tool_input", {}).get("command", "")
if "HOOK-TEST" in cmd:
    print("テスト用の文字列を含むので止めました", file=sys.stderr)
    sys.exit(2)
sys.exit(0)

echo HOOK-TEST を実行させて止まれば、その場所の settings.json は読まれています。 終了コード2で止めたとき、標準エラーに出した文は止めた理由として Claude に渡ります。

1. 起動したフォルダの設定しか読まれていない

最初に疑うのは置き場所です。CLAUDE.md は、起動したフォルダより上の階層にあるものまで読まれます。 ところがフックを書く .claude/settings.json で読まれるのは、セッションを起動したフォルダのものだけです。 公式ドキュメントにも、リポジトリのルートに置いた設定を使うならそこで起動するように、と書かれています。

自社の場合、フックは上の階層の設定に書いてあり、ふだんは1つ下のフォルダで起動していました。 指示書は上から読まれていたので、設定も読まれていると思い込んでいました。

直し方

フックのスクリプト本体は1か所に置き、起動するフォルダの settings.json に絶対パスで登録します。 本体を各フォルダにコピーすると、片方だけ直して食い違います。

読み込まれた設定ファイルは、Claude Code の中で /status を開くと「Setting sources」で確かめられるので、まずここを見てください。 登録されているフックそのものは /hooks で見られます。

もう1つ、別のPCや新しいフォルダでだけ動かないときは、フォルダの信頼の確認を疑ってください。 対話で使うセッションでは、そのフォルダを信頼すると承認するまで、どの設定ファイルのフックも実行されません。

出典: Settings files and precedence/Hooks reference(いずれも2026年10月15日に確認)

2. スクリプトが例外で落ちると、止めずに通す

配線を直しても、まだ止まらないことがありました。原因は終了コードです。

公式ドキュメントでは、操作を止めるのは終了コード2のときだけです。 1など、それ以外の0でないコードは「止めないエラー」として扱われ、操作はそのまま進みます。 つまり検査の途中でスクリプトが例外を出して落ちると、止めるはずの操作が通ってしまいます。

自社では、Windows で日本語のフォルダ名を含む出力を読むところで文字コードの変換に失敗し、毎回落ちていました。 そのせいで、ほとんどの操作でフックが素通しになっていました。

import subprocess
r = subprocess.run(cmd, capture_output=True)   # 文字列にせず bytes で受ける
out = r.stdout.decode("utf-8", errors="replace")

止めるためのフックなら、想定外のエラーで落ちたときにどちらへ倒すかも決めておきます。 止める側へ倒すなら、処理全体を try で囲み、例外のときに終了コード2を返します。

確かめるときは、本番と同じ入力を流してください。英字だけのパスで試して止まっても、 日本語のパスで落ちるなら本番では効いていません。

3. Windows のパスの書き方を取り違える

Windows で Git Bash を使っていると、パスが /c/Users/... の形で渡ってくることがあります。 これをそのまま Python で判定すると、思わぬ結果になります。

自社では、フォルダの存在確認が、本当は無い場所なのに「ある」と返し、その先の git の呼び出しで失敗しました。 Windows がこの書き方を「ドライブ直下の c フォルダからの相対パス」と読み、たまたまそのフォルダが実在したためです。

受け取ったパスは、判定する前に C:/Users/... の形へそろえます。 直すのは表記の読み方だけで、検査の条件には手を付けません。

import re
def win_path(p):
    m = re.match(r"^/([a-zA-Z])/(.*)", p)
    return m.group(1).upper() + ":/" + m.group(2) if m else p

Git Bash から呼ばれたときと PowerShell から呼ばれたときでは、渡ってくるパスの形が変わります。 どちらからも呼ばれるフックなら、受け取った直後にこの関数を1回通してから使ってください。 あわせて、受け取ったパスをそのままログに書き出しておくと、次に挙動が変わったときにすぐ見比べられます。

4. 解除キーが、説明文の中で効いてしまう

止めるフックには、人が意図して通すための解除キーを付けることがあります。 たとえば、決めた環境変数をコマンドの前に付けたときだけ通す、という形です。

自社では、この判定を「コマンドの文字列にキーが含まれているか」で書いていました。 すると、コミットメッセージに「解除キーを使った」と記録として書いただけで解除されました。 フックに渡るのはコマンドの文字列全体で、メッセージの本文もその中に入るからです。

いまは、キーがコマンドの先頭に環境変数として置かれたときだけ解除と見なしています。 判定に使うのは、キーが置かれている位置です。

症状から引く早見表

症状疑うところ確かめ方
一度も止まらない起動したフォルダと設定の置き場所/status の Setting sources
別のPCでだけ止まらないフォルダの信頼の承認起動時の確認を承認したか
止まるときと止まらないときがあるスクリプトの例外本番と同じ入力(日本語パスなど)を手で流す
Windows でだけ挙動が違うパスの表記受け取ったパスをログに出す
誰も解除していないのに通る解除キーの判定キーを含む説明文で試す
書き換えたのに古い動きのまま今の登録内容/hooks で登録を見る

フックの実行記録は、claude --debug で起動するとログファイルに残ります。 画面には出ず、~/.claude/debug/ の下に書かれます。

フックに任せるもの、任せないもの

フックが向いているのは「あるかないか」で決まる検査です。危ないコマンドの形、触ってはいけないファイル、上限を超える量。 判断の要る確認まで載せると、誤って止まる回数が増え、そのうち外されてしまいます。

何を止めるかの決め方は止める仕組みは3層で作るに、 導入の全体の順番はClaude Codeの社内導入で最初の30日に決めることに書きました。

よくある質問

settings.json を書き換えたら、再起動が要りますか

通常は要りません。公式ドキュメントには、設定ファイルのフックを直接書き換えた場合はファイルの監視で自動的に反映される、とあります。 自社でも書いた直後から効いていました。反映されていないように見えたら、/hooks で今の登録を確かめてください。

matcher に書いたツールで発火しません

記号を含まない名前なら、matcher は完全一致で比べます。Bash は Bash ツールだけ、Edit|Write はどちらか一方に当たるという意味です。 それ以外の記号を含むと正規表現として扱われ、Edit.* は NotebookEdit にも当たります。

PowerShell でフックを書けますか

書けますよ。コマンドのフックに "shell": "powershell" を付けると、PowerShell で実行されます。

hooks と権限の設定(permissions)は、どう使い分けますか

決まった操作をまるごと禁止するだけなら、permissions の deny で十分です。 中身を見て決めたいとき、たとえば変更の量が上限を超えるか、特定の文字列を含むかで止めたいときは hooks を使います。

フックが、こちらの意図した作業まで止めてしまいます

自社でも、検査対象の語をコミットメッセージに書いただけで止まったことがあります。 このときは解除キーを使わず、メッセージの書き方を変えて通しました。 止まりすぎるなら条件を見直し、解除キーは人が使うと決めたときだけにしておきます。

フックのせいで操作が遅くなります

コマンドのフックの待ち時間は、既定で600秒です。デバッグのログには所要時間が残るので、どのフックが遅いかを先に確かめてください。

出典: Hooks reference

この記事の内容を、御社の業務に合わせて作ります

ここに書いた作業ルール・止める仕組み・公開前チェックは、そのまま Claude Code 導入パックの納品物です。 御社の業務・フォルダ構成・用語に合わせて組み直してお渡しします。

「まだ何を任せるか決まっていない」段階の相談でも構いません。 最初の30分は、任せてよい業務を一緒に選ぶ時間に使います。

相談する(問い合わせフォーム)