Claude Codeを個人プロジェクトで毎日使っていると、git push は1日に何度も走る。そのたびに秘密情報の混入を確かめ、別タブで積まれたcommitを巻き込んでいないか確認したい。だが、手動の確認は抜ける。自分でも忘れるし、Claudeに任せても忘れる。
そこで、レビューが済んでいなければpushを止めるhookを作った。止めるだけなら単純だが、「さっきレビューした差分」と「いまpushする差分」が同じかを確かめる必要があった。複数タブで作業していると、レビュー後にもHEADが変わるからだ。
以下は、2026年4月19日時点の個人環境で使っていた構成と、その運用記録である。
レビューで確かめる二つのこと
push前に確認したいのは、秘密情報とcommitの範囲だ。
- APIキー、トークン、メールアドレス、
.envの中身がcommitに混ざっていないか origin/main..HEADに、自分の担当外のcommitが含まれていないか
秘密情報の一部は機械スキャンで検出できる。ただ、秘密ではなくても公開したくない私的な情報まで、パターンだけで拾い切るのは難しい。commitの範囲も、並行作業する別タブがHEADに変更を積むなら、push直前の状態を見る必要がある。
このため、Claudeのサブエージェントであるsecurity-reviewerに差分を読ませ、その結果を確認してからpushする運用にしていた。
指示ファイルから実行前のチェックへ
最初はCLAUDE.mdや .claude/rules/ に、push前にsecurity-reviewerを呼ぶよう書いていた。しかし、長いタスクに集中していると、その工程が抜ける。ルールを読んでいても、実行時に適用されるとは限らなかった。
Claude CodeのPreToolUse hookは、ツールの実行直前にスクリプトを走らせ、許可・拒否を判定する仕組みだ。これを使い、Bash ツールで git push が実行される前に、レビュー済みかを確認することにした。
.claude/settings.json の hooks.PreToolUse に登録した設定は、次の形である。Bash を対象に、ガード用スクリプトを呼び出す。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 .claude/hooks/git-push-guard.py"
}
]
}
]
}
}
当時の実装では、スクリプトが $CLAUDE_TOOL_INPUT から実行予定のコマンドを読み、git push を含む場合だけチェックする。それ以外のコマンドは、そのまま通す。
レビュー済みであることは、logs/security-review/ のマーカーファイルで記録する。security-reviewerがレビューを終えると、対象commit範囲のhashを名前に含むマーカーを作る。hookはpush時点の範囲に対応するマーカーがあるかを調べ、なければ拒否する。
レビュー後にcommitが増えた場合
マーカーには、どの差分をレビューしたかを結びつけなければならない。レビュー済みcommitの記録があるだけでは、後から加わった変更は分からない。
例えば、次の順で作業が進む。
- 自分がcommit Aを作り、レビューを終えてmarker(A)を作る
- 別タブがcommit Bをmainへ積み、HEADが進む
- 自分がpushしようとする
ここでAのマーカーが存在することしか確認しなければ、未レビューのBまでpushしてしまう。実行時点の対象との照合が必要だ。
自分の実装では、origin/main..HEAD に含まれるcommit列全体をhash化した range hash を使った。別タブがcommitを追加すると列が変わり、レビュー時とは異なるhashになる。計算は次のように書ける。
RANGE_HASH=$(git log --pretty=format:%H origin/main..HEAD | sha256sum | cut -c1-16)
MARKER="logs/security-review/reviewed-${RANGE_HASH}.txt"
security-reviewerはレビュー時の範囲からマーカーを作り、hookはpush直前の範囲から同じ名前を計算する。範囲が変わればマーカーが見つからず、再レビューが必要になる。この仕組みは、当時の運用でpush対象として扱っていた origin/main..HEAD に対応している。
さらに、マーカーはsingle-use(1回のpushで消費)とし、放置した古いマーカーを片づけるためにTTLを30分にした。range hashで対象を結びつけ、single-useで再利用を防ぎ、TTLで古い記録を残さない。それぞれ役割が違う。
並行作業で得られた手応え
運用してみると、レビュー忘れを止める以外にも効果があった。security-reviewerに差分を読ませるたび、「この範囲には担当外のcommitが混ざっている」と気づける。複数タブで作業する中で、これによって何度か事故を防いだ。
pushの粒度も変わった。レビューを通す手間があるため、ひとまずpushする回数が減り、意味のある変更のまとまりごとに送るようになった。
この運用では、レビューを終えた印としてマーカーを作り、hookはその存在と対象範囲を確認する。ファイルがあることだけで、レビューが実際に行われたか、その内容が適切かまで確かめられるわけではない。それでも、レビュー完了時に記録を残す運用と組み合わせることで、覚えているかどうかに左右されていた工程の抜けを、pushの直前で止められるようになった。
以前は、pushのたびに「確認を忘れていないか」という小さな不安があった。導入後は、レビュー済み記録の確認をhookに任せ、差分の内容に注意を向けられるようになった。自分にとっては、ルールを覚え続ける負担を減らすための、具体的な装置になった。