◉CLAUDE LABEN
●THREADS — Messages API に message threads がベータで登場しました(11/2)。会話をサーバーに保持し、以降は新しいメッセージだけを送ります●CONSOLE — Console の IP 許可リストがベータで登場しました(11/5)。許可外のアドレスからのリクエストは拒否されます●SONNET 4.5 — Claude API での提供終了は 11/30、残り25日。移行先は Sonnet 5.5●WINDOWS — 日本語の環境でだけフックが黙って壊れる報告があり、文字コードの確認が呼びかけられています●FAST — Opus 4.8 の fast mode は非推奨になり、11/30 に除去されます。移行先は Opus 5.5●NEW — Cowork に渡したフォルダの先頭に、十行の約束ファイルを置いてみました●THREADS — Messages API に message threads がベータで登場しました(11/2)。会話をサーバーに保持し、以降は新しいメッセージだけを送ります●CONSOLE — Console の IP 許可リストがベータで登場しました(11/5)。許可外のアドレスからのリクエストは拒否されます●SONNET 4.5 — Claude API での提供終了は 11/30、残り25日。移行先は Sonnet 5.5●WINDOWS — 日本語の環境でだけフックが黙って壊れる報告があり、文字コードの確認が呼びかけられています●FAST — Opus 4.8 の fast mode は非推奨になり、11/30 に除去されます。移行先は Opus 5.5●NEW — Cowork に渡したフォルダの先頭に、十行の約束ファイルを置いてみました
記事一覧/Claude Code
⟐ Claude Code/2026-06-12中級

Claude Code が破壊的なコマンドを打つ前に止める — PreToolUse フックの事前検査

長時間の自動実行中に rm -rf や force push が紛れ込む不安を、PreToolUse フックの事前検査で解消した記録です。settings.json の設定、検査スクリプト、exit code 2 でブロックされた後の Claude の挙動、誤検知の調整までを実例で示します。

Claude Code283Hooks4PreToolUse5permissions7安全運用

深夜、長めのリファクタリングを Claude Code に任せて別の作業をしていたときのことです。ふと実行ログを遡って、手が止まりました。ビルド成果物を消すための rm コマンドが、変数の展開結果しだいではプロジェクト直下を対象にし得る形で書かれていたのです。実害はありませんでした。それでも、「実行される前に機械的に検査する仕組み」を持っていないことが、急に心許なくなりました。

個人開発には、コマンドをレビューしてくれる同僚がいません。私自身、エージェントに任せる時間は今後も増えていく見込みだったので、このタイミングで PreToolUse フックによる事前検査を組み込みました。導入から数週間、実際に何度か「止まってくれた」場面があり、構成としても運用としても手応えを感じています。その過程で分かったことを、設定ファイルと検査スクリプトの実物とあわせて残しておきます。

許可ダイアログがあるのに、なぜ別の検査が要るのか

Claude Code には標準で許可確認の仕組みがあります。それでも検査の層をもう一枚足したのは、理由が2つあります。

1つ目は、自動承認と組み合わせる場面が現実に多いことです。長時間のタスクでは Bash の実行を許可リストに載せたり、確認を省略する設定で走らせたりします。便利さと引き換えに、「人間が読んでから実行する」という前提はそこで崩れます。

2つ目は、確認疲れです。何十回も続けて承認していると、ダイアログの中身を読まずに通すようになります。少なくとも私はそうなりました。人間の注意力は、残念ながら安全装置として当てになりません。読まなくなった人間の代わりに、毎回同じ集中力で読んでくれる検査役を置く。それが PreToolUse フックの位置づけです。

PreToolUse フックが介入できるタイミング

PreToolUse は、Claude Code がツールを実行する直前に呼ばれるフックです。フックに登録したコマンドは標準入力から JSON を受け取ります。Bash ツールの場合、tool_name と、実行されようとしているコマンド文字列を含む tool_input が入っています。

押さえておきたいのは終了コードの意味です。

  • exit 0 — 実行を許可します
  • exit 2 — 実行をブロックし、標準エラー出力の内容を Claude にフィードバックします
  • それ以外の非ゼロ — エラーとして扱われますが、ブロックにはなりません

この「2 だけが特別」という仕様は見落としやすく、私は最初 exit 1 で書いてしまい、何もブロックしない検査スクリプトをしばらく動かしていました。判定に引っかかったら必ず 2 を返す。ここを先に押さえておくと、無駄なデバッグが減ります。

settings.json への最小構成

プロジェクト単位で効かせる場合は .claude/settings.json に書きます。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python3 .claude/hooks/check_bash.py"
          }
        ]
      }
    ]
  }
}

matcher はツール名へのマッチです。ここを空にすると全ツールの実行前に呼ばれてしまうため、Bash に絞っています。ファイル編集系まで検査対象を広げると誤検知の調整が一気に難しくなるので、まずコマンド実行だけを見るのが現実的です。

設定をリポジトリにコミットしておけば、同じリポジトリを触る別マシンでも同じ検査が効きます。私はこの点を重視していて、まず1つのリポジトリで2週間ほど運用してから、~/.claude/settings.json に昇格させて全プロジェクト共通にしました。

検査スクリプトの実装

検査の本体は Python で書きました。「消えたら取り返しがつかない操作」だけを止める、控えめな拒否リストです。

#!/usr/bin/env python3
# .claude/hooks/check_bash.py
import json
import re
import sys
 
BLOCK_PATTERNS = [
    (r"rm\s+-[a-zA-Z]*r[a-zA-Z]*f?\s+(/|~|\$HOME)(\s|$)",
     "ルートやホームディレクトリへの再帰削除は許可していません"),
    (r"git\s+push\s+(?!.*--force-with-lease).*--force",
     "force push は --force-with-lease を使ってください"),
    (r"chmod\s+(-R\s+)?777",
     "chmod 777 は許可していません。必要な権限を個別に指定してください"),
    (r"curl\s+[^|;]*\|\s*(ba|z)?sh",
     "ダウンロードしたスクリプトの直接実行は許可していません"),
    (r">\s*/dev/sd[a-z]",
     "ブロックデバイスへの直接書き込みは許可していません"),
]
 
def main() -> int:
    data = json.load(sys.stdin)
    if data.get("tool_name") != "Bash":
        return 0
    command = data.get("tool_input", {}).get("command", "")
    for pattern, reason in BLOCK_PATTERNS:
        if re.search(pattern, command):
            print(f"blocked by check_bash.py: {reason}", file=sys.stderr)
            return 2
    return 0
 
if __name__ == "__main__":
    sys.exit(main())

許可リスト方式(許可したコマンド以外を全部止める)も検討しましたが、最初の1週間で捨てました。エージェントが使うコマンドの幅は想像より広く、許可リストが育つまでの間、ほとんど何も実行できなくなるからです。拒否リストで「致命傷だけ防ぐ」線から始めて、ヒヤリとした実例が出るたびにパターンを足す。個人開発の規模では、この育て方が一番長く続きます。

ブロックされた後、Claude はどう動くか

導入して最初に面白かったのは、ブロックされたときの Claude の振る舞いです。exit 2 で返した標準エラーの文面は、そのまま Claude へのフィードバックになります。

実際にあった例では、git push --force を止めたとき、Claude は文面にある「--force-with-lease を使ってください」を読み取り、自分でコマンドを書き換えて再実行してきました。つまり、うまく書いたエラーメッセージは、禁止ではなく誘導として機能します。

この挙動が分かってからは、メッセージを「何がだめか」ではなく「代わりにどうするか」が伝わる形に直しました。blocked とだけ返すと、Claude は原因を推測して別の回避策を探し始めることがあり、かえって挙動が読みにくくなります。

数週間の運用で調整した点

実際に回してみて、手を入れたのは次の3箇所でした。

  • 一時ディレクトリの誤検知: テストが使う $TMPDIR 配下の再帰削除まで止めていました。パターンを見直し、対象を /・~・$HOME の直指定に絞って解消しています。
  • ブロックログの記録: 何を止めたかを ~/.claude/hooks.log に追記するようにしました。週に一度見返すと、パターンを足す判断にも削る判断にも、このログがそのまま根拠になります。
  • 検査の軽さの維持: このスクリプトはツール実行のたびに走るので、起動の重い処理を入れると体感がはっきり悪くなります。外部コマンドの呼び出しやネットワークアクセスは入れない、が私の結論です。

permissions の deny ルールとの住み分け

Claude Code には設定ベースの deny ルールもあり、最近のアップデートで glob によるパターン指定にも対応しました。「設定で書けるなら、スクリプトは不要では」と考えて両方試しましたが、いまは役割分担に落ち着いています。

  • 静的に決まる禁止事項(特定ディレクトリへの書き込み禁止、特定コマンドの使用禁止)→ permissions の deny ルール
  • 文脈を見て判断したい事項(引数の組み合わせ、リダイレクト先、代替案をメッセージで誘導したいもの)→ PreToolUse フック

deny ルールは宣言的で高速、フックは表現力で勝ります。どちらかに寄せるより、単純な禁止を deny 側へ逃がしてフックの検査パターンを最小限に保つ構成が、メンテナンスの負担としては一番軽くなりました。

最初の一歩は1ファイルから

この仕組みは、check_bash.py を1ファイル置いて settings.json に数行足すだけで動き始めます。まずは拒否パターンを2つか3つに絞って1週間運用し、ブロックログを眺めてから育てる順番がおすすめです。誤検知に悩まされる前に、「止まってくれた」という最初の体験が得られるはずです。

シェア

お読みいただきありがとうございます

Claude Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

⟐ Claude Code2026-10-19
MCP の allow を `mcp__サーバー名` で書いていた人へ — v2.1.299 の変更で見直すのは名前に `__` を含むサーバーだけ
Claude Code v2.1.299 で MCP の allow ルールが「名指ししたサーバーだけ」に一致する形へ変わりました。裸の mcp__サーバー名 が影響を受けるのは、名前に __ を含む、または末尾が _ のサーバーだけです。点検用の Python スクリプト付き。
⟐ Claude Code2026-10-18
allow を足したのに承認画面が消えない — /permissions で「どの層が決めたか」を6段で読む
settings に allow を書いたのに、同じ承認画面が出続けるときの切り分けです。ask・deny の先勝ち、settings.local.json の置き場所、Cowork の Bash など、公式の権限ドキュメントで確認できる6つの層を、/permissions の表示から順に読みます。
⟐ Claude Code2026-10-15
日本語フォルダ名を守るはずのフックが、一度も止めてくれませんでした — Python の stdin が cp932 で読んでいた話
「経理」「議事録」などの日本語フォルダを守る PreToolUse フックが、Python の stdin 既定が cp932 の環境では素通りします。再現の手順と 3 行の直し方、確認用の 1 コマンドを書き残します。