先週の火曜日、公開リポジトリにAPI keyをプッシュしそうになった。Claude Code hookがコミットの0.5秒前にそれを検知してくれた。その瞬間、私は「hooksはあればいい機能」から「これなしでagentを動かすことはしない」へと切り替わった。
Claude Code hooksはこのツール全体で最も過小評価されている機能で、私のタイムラインではほぼ誰も使っていない。それを変えよう。私がソロで開発する際に実際に使っているhooks、それを支える設定、そして本当にリポジトリを救った1つのhookを紹介する。
hookとは実際のところ何か
hookは、何かが起きたときにClaude Codeが自動的に実行するshellコマンドだ。ツールが動く前。ファイルが編集された後。agentが終了したとき。Claudeに「覚えておいてください」とお願いするのではなく、harnessが毎回確実にやってくれる。この違いは重要だ。フォーマッターを「たいてい覚えて」実行するagentは、最悪のタイミングで忘れるagentだ。
hooksはsettings.jsonに書く。構造はこうなっている:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "prettier --write \"$CLAUDE_FILE_PATHS\"" }
]
}
]
}
}
これはClaudeがファイルを編集した瞬間に、そのファイルにPrettierを走らせる。フォーマットについて考えることはもうない。diffは常にきれい。「おっと、空白の変更が400件」がコミットを汚すこともない。
リポジトリを救ったhook
これがAPI keyのhookだ。BashへのPreToolUse hookで、git commitやgit pushをスキャンし、ステージされたdiffにsecretらしきものがないかgrepする。
{
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "~/.claude/scripts/secret-scan.sh" }
]
}
]
}
スクリプトはステージされた変更をsk-、AKIA、そして私自身のいくつかのプロバイダープレフィックスのパターンでgrepする。ヒットすると、スクリプトは非ゼロで終了し、PreToolUse hookからの非ゼロ終了はアクションをブロックする。Claudeは文字通りそのコマンドを実行できない。理由を伝えられ、回避策を探る。
先週の火曜日にこれが発動した。agentにconfigのリファクタリングをさせたところ、読んではいけないenvファイルからkeyをインラインで埋め込んでいた。hookがなければ、そのkeyはGitHubに公開され、深夜にcredentialをローテーションし、誰かに私のコストでcryptoをマイニングされていたかもしれない。hookのおかげで、1行のブロックメッセージを受け取り、先に進めた。bashの12行。私が持つ中で最もコスパの良い保険だ。
日常のhooks
上記2つに加えて、本当に価値を発揮しているものがある。
.tsファイルの編集後にtype checkerを走らせ、エラーをagentに直接フィードバックするPostToolUse hook。Claudeは私がペーストしなくても自分の型エラーを確認できる。同じターンで自己修正する。このフィードバックループだけでもセットアップする価値がある。私が読む前にagentがエラーを修正してくれる。
Claudeが長いタスクを終えたときにデスクトップ通知を発火するStop hook。大きなリファクタリングをagentに任せて昼食を作りに行く。通知があれば、タマゴッチのように90秒ごとにターミナルを確認する必要がない。
{
"Stop": [
{ "hooks": [{ "type": "command", "command": "osascript -e 'display notification \"Claude is done\" with title \"CC\"'" }] }
]
}
小さな変更。でも働き方が完全に変わった。タスクを始めて、ping が来るという本当の確信を持って席を立てる。
削除したhook
すべての編集後に自動でテストスイート全体を実行するhookを試したことがある。責任感があるように聞こえた。最悪だった。スイートに40秒かかるため、小さな編集のたびに40秒止まり、agentはアイドル状態で私の午後を無駄にしていた。教訓:hooksは高速かasyncでなければならない。遅い同期hooksはキビキビとしたagentを糖蜜に変える。
今はテストをStop hookで一度だけ、最後に実行している。高速チェック(lint、型、secretスキャン)はインラインで実行。遅いチェックは境界で実行。hookのコストを発火頻度に合わせること。すべての編集で発火するなら、1秒以内に戻ってくれなければ困る。
どこに置くか
プロジェクト固有のhooksはリポジトリの.claude/settings.jsonに入れ、コードと一緒に納品してコラボレーターにも届くようにする。デスクトップ通知のような個人的なものは~/.claude/settings.jsonに入れ、すべてのプロジェクトで追いかけてもらう。secretスキャナーはグローバルに置くこと。例外なくどこでも欲しい、「後で追加しよう」はなし。
1時間を無駄にさせた罠:hookコマンドは非インタラクティブshellで動く。コマンドが.zshrcの何かに依存しているなら、そこにはない。prettierがターミナルでは動いて、hookでは静かに何もしなかった——PATHが違ったから——というときに初めて気づいた。絶対パスかnpxを使うこと。
1つから始めよう
今日これを全部作る必要はない。secretスキャナーを追加して。それだけで十分。12行あれば、穏やかな火曜日とcredentialローテーションの消火訓練の差になる。
信頼できるようになったら、フォーマッターを追加する。次に通知。1週間もすれば、きれいにフォーマットし、secretをブロックし、自分の型エラーを修正し、完了したら肩を叩いてくれるagentができあがる——そして4つのことを手動でやらなくなる。
これが1日1時間だ。1つの大きなタスクではなかった。40の小さなタスクで、今はコーヒーを取りに行く間にshellスクリプトがそれをやってくれている。
