CLAUDE LABEN
VERSION — 8月10日に v2.1.227 が出ていました。新機能はなく、プラン判定と CI まわりの不具合修正が中心ですAUTO — 8月14日の既定化まで残り2日です。Pro・Max・Team で Claude Code の auto モードが既定で有効になりますCI — claude-code-action で allowed_non_write_users を指定すると GitHub ホストランナー上の Bash が全て失敗していた問題が直りましたBILLING — 期限切れトークンで開始したセッションが加入プランを取り違え、Max 利用者に使用クレジットの有効化を促していた不具合も解消していますSUNSET — 旧 Workbench と実験的プロンプトツール API の廃止は8月17日で、残り5日ですPRICE — Sonnet 5 のプロモ価格 $2/$10 per Mtok は8月31日までで、9月1日から $3/$15 per Mtok になりますVERSION — 8月10日に v2.1.227 が出ていました。新機能はなく、プラン判定と CI まわりの不具合修正が中心ですAUTO — 8月14日の既定化まで残り2日です。Pro・Max・Team で Claude Code の auto モードが既定で有効になりますCI — claude-code-action で allowed_non_write_users を指定すると GitHub ホストランナー上の Bash が全て失敗していた問題が直りましたBILLING — 期限切れトークンで開始したセッションが加入プランを取り違え、Max 利用者に使用クレジットの有効化を促していた不具合も解消していますSUNSET — 旧 Workbench と実験的プロンプトツール API の廃止は8月17日で、残り5日ですPRICE — Sonnet 5 のプロモ価格 $2/$10 per Mtok は8月31日までで、9月1日から $3/$15 per Mtok になります
記事一覧/Claude Code
Claude Code/2026-06-14上級

Claude Code の Hooks で品質ゲートを壊さず回す — exit code の意味・ブロック挙動・観測設計の実装メモ

Claude Code Hooks を自動化の安全弁として運用するための実装メモ。exit code 0/2/その他の使い分け、JSON 出力による決定制御、ブロックが暴発したときの切り分け、観測ログの設計までを実例で整理します。

claude-code129hooks15automation54ci2reliability14

プレミアム記事

個人開発で複数のサイトを無人運用している私自身、Claude Code を自動で回すようになって最初に痛い目を見たのは、自分で仕込んだ PostToolUse フックが原因でセッションが止まったときでした。フォーマッタを「ファイルを書いたら自動で整える」つもりで PostToolUse に載せたところ、整形でファイルが書き換わり、それがまた次の編集として扱われ、フォーマッタが再び走る、という往復に近い挙動になったのです。

Hooks は「AI にお願いする」のではなく「必ずこうなる」を担保する仕組みなので、便利な反面、設計を誤ると自動運用そのものを壊します。ここでは公式ドキュメントに載っている挙動を踏まえつつ、自動投稿パイプラインで実際に運用して分かった「壊さないための勘所」を、コードと一緒に整理していきます。

まず押さえるべきは「終了コードと標準出力の契約」

Hooks の挙動を理解するうえで最初に固めておきたいのは、フックが Claude Code に何を返すか、という契約です。ここが曖昧なまま複雑なスクリプトを書くと、ブロックしたいのに素通りしたり、逆に通したいのに止まったりします。

フックは大きく二つの方法で結果を返します。ひとつは終了コード、もうひとつは標準出力に流す JSON です。

終了コードの意味は次の三つに整理できます。

  • exit 0 — 正常終了。標準出力の内容は、フックの種類によって扱いが変わります(後述)。
  • exit 2 — ブロッキングエラー。標準エラー出力の内容が Claude にフィードバックされ、操作が止められます。
  • それ以外(exit 1 など)— 非ブロッキングエラー。警告としてユーザーに表示されますが、処理は続行します。

この三分法のうち、自動化で最も重要なのは exit 2 です。「危険なコマンドを止める」「規約違反の編集を差し戻す」といったゲートは、すべて exit 2 と標準エラー出力の組み合わせで成立します。逆に言えば、ブロックしたいのに exit 1 を返していると、警告は出るのに処理は通ってしまうので、ゲートとして機能しません。

#!/usr/bin/env bash
# block-force-push.sh — PreToolUse(Bash) で危険な push を止める
input=$(cat)                      # フックは JSON を stdin で受け取る
cmd=$(echo "$input" | jq -r '.tool_input.command // empty')
 
if echo "$cmd" | grep -qiE 'git +push.*(--force|-f)\b'; then
  # stderr に理由を書いて exit 2 → Claude にフィードバックされ操作が止まる
  echo "force push はこのリポジトリでは禁止です。--force-with-lease を検討してください。" >&2
  exit 2
fi
exit 0

ここでのポイントは三つあります。フック入力は引数ではなく標準入力に JSON で渡ること、ブロック理由は標準出力ではなく標準エラー出力に書くこと、そして判定に使うフィールド(tool_input.command)を正確に取り出すことです。私は最初、理由を標準出力に書いてしまい、ブロックはされるのにフィードバックが Claude に届かない、という分かりにくい状態に陥りました。

JSON 出力で「止める/続ける」を明示的に制御する

終了コードによる制御はシンプルで頑健ですが、表現力には限界があります。「止めるが、こういう理由で」「続けるが、追加のコンテキストを渡す」といった細かい制御をしたいときは、標準出力に JSON を流す方法が向いています。

PreToolUse の場合、次のような JSON を返すと、終了コードに頼らずに許可・拒否を表現できます。

#!/usr/bin/env bash
# guard-writes.sh — PreToolUse(Write|Edit) で保護パスへの書き込みを拒否
input=$(cat)
path=$(echo "$input" | jq -r '.tool_input.file_path // empty')
 
case "$path" in
  *.env|*/secrets/*|*/.git/*)
    cat <<JSON
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "保護対象のパスです($path)。意図的な変更なら手動で行ってください。"
  }
}
JSON
    exit 0
    ;;
esac
exit 0

JSON 方式の利点は、終了コードを 0 に保ったまま「拒否」を表現できる点です。これにより、フック自体のエラー(スクリプトのバグで非ゼロ終了)と、業務ロジックとしての拒否を切り分けられます。運用していると、この切り分けが後でとても効いてきます。「フックが落ちたのか」「ルールで弾いたのか」がログ上で区別できるからです。

Stop フックでは decision フィールドが使えます。"decision": "block" を返すと Claude の停止を差し止め、reason に書いた内容に従って作業を続行させられます。たとえば「テストが緑になるまで止まるな」というゲートはこう書けます。

#!/usr/bin/env bash
# require-green-tests.sh — Stop フックでテスト未通過なら続行を強制
input=$(cat)
# 無限ループ防止: 既にこのフックが止めた状態なら、もう止めない
if [ "$(echo "$input" | jq -r '.stop_hook_active // false')" = "true" ]; then
  exit 0
fi
 
if ! npm test --silent >/tmp/test.log 2>&1; then
  cat <<JSON
{"decision": "block", "reason": "テストが失敗しています。/tmp/test.log を確認し、修正してから完了してください。"}
JSON
  exit 0
fi
exit 0

このスクリプトで最も重要なのは stop_hook_active のチェックです。これが無いと、Stop フックがブロック→作業→また Stop→またブロック、という終わらない往復を生みます。後述しますが、自動運用での事故の多くはこの「ループの口」が塞がれていないことに起因します。

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

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
exit code 2 と JSON 出力 decision のどちらでブロックすべきか、PreToolUse・Stop ごとの判断基準
フォーマッタやリンタを Hooks に載せたときに無限ループ・誤ブロックを起こさない安全設計
フックの発火・所要時間・ブロック回数を JSONL で記録し、暴発を後から追える観測ログの実装
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

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

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

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

関連記事

Claude Code2026-07-14
push先がいつの間にか一つ増えていた — /commit-push-pr が origin 以外のリモートへも押し始めた変更に、許可リストで蓋をする
7月14日の更新で /commit-push-pr が origin に加えて設定済みの push リモートへも自動で押すようになりました。便利な一方、ミラーやバックアップ用の第二リモートを持つ環境では、意図しない push が静かに増えます。どのリモートへ押し得るかを棚卸しし、許可リスト外の push を pre-push フックで止め、無人実行でも安全に回す設計を実装込みで整理しました。
Claude Code2026-07-03
5分の沈黙で誰かがやり直しを始める — streaming idle watchdog 既定化で数え直すリトライの責務
Claude Code の streaming idle watchdog 既定化で、実行スタックに再試行する層がもう一枚増えました。SDK・自前ラッパー・watchdog・スケジューラの4層を棚卸しし、最悪ケースの試行回数を掛け算で見積もり、リトライ責務を一層に畳む設計をまとめます。
Claude Code2026-06-12
Claude Code の fallbackModel を三段構成にする — 過負荷の朝でも止まらない無人実行の設計記録
Claude Code の fallbackModel を三段構成で運用し、過負荷時も夜間バッチを止めない設計を実測つきでまとめます。実行モデルの記録方法、下位モデルに落ちた日の品質差への備え、deny ルール併用まで。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →