Hookのexit 2とstderrを基礎から解説


Claude Code の Hook(フック)を調べていると、こんな設定をよく見かけます。

exit code を 2 にして、stderr にメッセージを書く

「これでClaudeに通知が届く」と説明されるのですが……そもそも exit code って何? stderr って何? ここが分からないと、なぜ動くのか腑に落ちませんよね。私もここでしばらくモヤモヤしていました。

今回は、この2つを料理の例えで基礎から解説します。仕組みが分かると、Hook を自分で書くときに「なぜこう書くのか」が見えてきます。

※ Hook の基本(そもそも何ができるか、デスクトップ版での設定手順)はこちらの記事にまとめています。この記事は「通知の仕組み」に絞って深掘りします。

そもそもプログラムは「3つのもの」を返す

ターミナルでコマンドやスクリプトを動かすと、実は3種類のものが返ってきます。レストランで料理を注文する場面で例えると分かりやすいです。

あなたがシェフ(=プログラム)に「オムレツ作って」とお願いしたとします。

返ってくるもの料理での例えプログラム用語
料理そのもの完成したオムレツstdout(標準出力)
報告・文句「卵が足りなかったよ」stderr(標準エラー出力)
成功/失敗のサイン親指👍 or 👎exit code(終了コード)

この3つが別々のルートで返ってくる、というのがポイントです。1つずつ見ていきます。

stdout:普通の「結果」を出す場所

プログラムが出すメインの出力です。普段ターミナルで目にする文字は、だいたいこれです。

たとえばファイル一覧を表示すると、こんな結果が返ってきます。

記事1.md  記事2.md  画像フォルダ

この一覧が stdout。「これが結果ですよ」という正規の出口です。料理でいえば、お皿に乗って出てくるオムレツそのものですね。

stderr:エラーや「お知らせ」専用の別ルート

ここが少し分かりにくいところです。「結果」とは別に、「エラーや報告」を流すための専用の出口があります。

なぜ分けるのか? それは、「正常な結果」と「エラーメッセージ」がごちゃ混ぜにならないようにするためです。

たとえば、存在しないファイルを開こうとすると、こんなメッセージが出ます。

そんなファイルないよ

この「ないよ」は stderr から出ています。料理に例えると、オムレツが出てこなかった代わりに、「材料がなかった」という報告が別ルートで届くイメージです。

そして大事なのが、stderr は「エラー」専用に見えて、実は「お知らせ」全般に使えること。今回のHookでは、これを「Claudeへの伝言ルート」として活用します。

exit code:成功か失敗かを表す「数字」

プログラムは終わるとき、必ず1つの数字を残します。これが exit code です。文字ではなく、ただの数字です。

ルールはシンプルです。

数字意味
0「成功!問題なし」👍
0 以外(1, 2, 127…)「何か起きた」👎

料理の例えなら、シェフが最後に出す親指サイン。「うまくできた(0)」か「ダメだった(0以外)」かを、たった1つの数字で教えてくれます。

普段あなたは exit code を意識していないかもしれませんが、裏では必ず付いていますgit push が成功したか失敗したかも、システムはこの数字で判断しているんです。

なぜ「exit 2 + stderr」でClaudeに届くのか

ここまで分かれば、本題はもうすぐです。

Claude Code には、こんな特別ルールがあります。

exit codeHookでの挙動
0「成功、問題なし」→ 何も起きない
2Claudeに伝えたいことがある」→ stderr の内容が Claude に届く
その他「エラー」→ あなた(人間)に表示されるだけ

つまり、2 は「Claude、聞いて!」という呼び鈴🔔。そして **stderr に書いた文章が「その伝えたい中身」**📄 なんです。

stderr に「テストを忘れずに」と書く
exit code を 2 にして終了

これで Claude Code が「お、exit 2 だな。stderr の中身を Claude に渡そう」と判断し、メッセージが届きます。

呼び鈴だけ鳴らしても中身がない、中身だけ書いても呼び鈴を鳴らさないと気づかれない。だから この2つはセットで意味を持つわけです。

PostToolUse と PreToolUse で意味が変わる

面白いのが、同じ「exit 2 + stderr」でも、Hookが走るタイミングで意味が変わることです。

タイミングexit 2 + stderr の意味
PreToolUse(処理の前)その操作をやめさせる🚫+理由を伝える
PostToolUse(処理の後)操作はもう済んでいるので止められない → 追加で伝えるリマインド📬

たとえば私は、「記事を編集したら検証を促す」Hook を PostToolUse で作っています。編集はもう成功しているので、ブロックではなく「編集したよ、検証してね」という事後のお知らせとして届く、という仕組みです。

ちなみに、実際のスクリプトはこんな感じです(一部抜粋)。

if (記事ファイルだったら) {
  process.stderr.write('記事を編集しました。検証してください');
  process.exit(2);   // ← ここで「2」を出す
}
process.exit(0);     // それ以外は何もせず素通り

編集自体は成功しているのに、わざと 2 を出してリマインドを届けている――これが「exit 2 + stderr」の正体です。

使い分けの注意:全部に付けない

ここで1つ注意点です。「exit 2 + stderr が便利だから」と、どんなHookにも機械的に付けるのはNGです。

  • Claude にメッセージを届けたい/操作をブロックしたい → exit 2 + stderr
  • ただ処理を走らせるだけ(ログ記録、コード整形など、通知が不要)→ exit 0(stderr は書かない)
  • 条件に当てはまらないときは → 必ず exit 0 で素通り

判断基準はシンプルで、「Claudeに伝えたいことがあるか?」だけです。伝えたいことがなければ、おとなしく 0 で終わらせましょう。

まとめ

  • プログラムは stdout(結果)・stderr(報告)・exit code(成功/失敗の数字) の3つを返す
  • stdout = 普通の結果、stderr = エラーやお知らせの別ルート、exit code = 0なら成功・それ以外は何かあった
  • Claude Code では exit 2 = 「Claudeに伝えたい」の合図stderr = その中身。だからセットで使う
  • PreToolUse なら「ブロック」、PostToolUse なら「リマインド」と、タイミングで意味が変わる
  • 通知が不要なHookは exit 0 が基本。全部に exit 2 を付けない

仕組みが分かると、コピペで設定していたHookが「なぜこう書くのか」までスッと理解できます。自分でHookを育てていくときの土台になるはずです。


📖 関連記事

🧑‍💻
この記事を書いた人
AI-Nyanko

某外資系コンサルのインフラエンジニア。Claude Code の実践ノウハウを失敗談も含めて正直に発信しています。

プロフィールを見る →