◉CLAUDE LABEN
●2.1.301 — vim の % モーション、VS Code パネルでの LaTeX 数式表示、使用量上限待ちのリトライ監視の上限が入りました(10/15)●2.1.299 — 200K トークン超で約54分放置した会話はアイドル圧縮。MCP の許可ルールは名指ししたサーバーだけに一致します●SONNET 4.5 — Claude API での提供終了は 11/30、残り45日。移行先は Sonnet 5.5●WINDOWS — 日本語の環境でだけフックが黙って壊れる報告があり、文字コードの確認が呼びかけられています●FAST MODE — Opus 5.5 の Fast mode がリサーチプレビューを終え、ベータヘッダーが不要になりました(10/15)●NEW — Cowork に渡したフォルダの先頭に、十行の約束ファイルを置いてみました●2.1.301 — vim の % モーション、VS Code パネルでの LaTeX 数式表示、使用量上限待ちのリトライ監視の上限が入りました(10/15)●2.1.299 — 200K トークン超で約54分放置した会話はアイドル圧縮。MCP の許可ルールは名指ししたサーバーだけに一致します●SONNET 4.5 — Claude API での提供終了は 11/30、残り45日。移行先は Sonnet 5.5●WINDOWS — 日本語の環境でだけフックが黙って壊れる報告があり、文字コードの確認が呼びかけられています●FAST MODE — Opus 5.5 の Fast mode がリサーチプレビューを終え、ベータヘッダーが不要になりました(10/15)●NEW — Cowork に渡したフォルダの先頭に、十行の約束ファイルを置いてみました
記事一覧/Claude Code
⟐ Claude Code/2026-06-14上級

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

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

claude-code132hooks22automation55cireliability15

✦ プレミアム記事

個人開発で複数のサイトを無人運用している私自身、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-04-30
Claude Code Skills を組み合わせる設計術 — 単一スキルでは届かない自動化を作る
Claude Code の Skills を1つずつ作るところから一歩進み、複数スキルを連携させて自動化を組む設計手法をお伝えします。Pipeline・条件分岐・メタスキルの3パターンとよくある落とし穴を実装例付きで詳述します。
⟐ Claude Code2026-04-24
Claude Code Hooks の本番デバッグ術 — ログが足りない時、どこから切り分けるか
ローカルでは動くのに本番で沈黙するClaude Code Hooksの切り分け方を体系化しました。まず疑うべきは出力先という視点、ログ不足を補う3層の可観測性、最小再現環境を60秒で作るテクニック、JSON入出力とexit codeのプロトコル把握、マルチユーザー環境の権限診断まで扱います。
⟐ Claude Code2026-03-28
Claude Code × Playwright で E2E テストを本番運用に乗せる — 落とし穴と導入の判断基準
Claude Code と Playwright を組み合わせて E2E テストを本番運用するための実装パターンと、導入効果の測り方。Cloudflare Workers/Pages preview deployment 下での落とし穴と、個人開発者がテストを導入する判断基準まで踏み込んで解説します。