コミットメッセージに入ってほしくない行があります。エージェントが付けがちな属性トレーラーとセッションへのリンクで、公開リポジトリには要りません。
最初は、エージェントがシェルを呼ぶ直前に検査を挟みました。git commit か gh を含むコマンド文字列を見て、該当すれば拒否する。これは動きます。動く範囲が狭いだけです。
素通りする 6 経路
同じ検査に、形を変えたコマンドを流して確かめました。
| 経路 | 結果 |
|---|---|
git commit -m "…" に直接書く | 拒否 |
git commit -F msg.txt | 素通り |
git commit --amend --no-edit | 素通り |
gh pr create --body-file body.md | 素通り |
| 実行時に文字列を組み立てる | 素通り |
git push(他所で書かれたコミット) | 素通り |
検査が読めるのはコマンド行だけです。メッセージがファイルに入った瞬間、あるいはシェル変数を経由した瞬間、そこには何も見えなくなります。
実際に混入していた 5 件は、別のマシンで書かれて pull されてきたものでした。この検査はそのうち 1 件も止められません。 止めたい対象と、見ている場所が噛み合っていませんでした。
もう 1 つ、同じ設定ファイルが PowerShell ツールを有効にしていながら、検査は Bash にしか掛かっていませんでした。ツールが 2 つあって片方しか見ていないので、入っているように見えて半分素通りします。
メッセージのある場所
git は 2 か所で、完成したメッセージそのものを渡してきます。
commit-msg— メッセージがファイルで渡る。-mも-Fも--amendもエディタ経由も、例外なくここを通る。pre-push— これから送るコミットを列挙できる。他所で書かれたものも、フックを入れる前に作ったものも、ここで捕まる。
上の表で素通りしていた 5 経路は、この 2 つで塞がりました。やったのは、書かれ方を数え上げるのをやめて、書かれた結果が必ず通る場所へ移すことだけです。
core.hooksPath をグローバルに設定すれば、マシン上の全リポジトリが対象になります。ただし .git/hooks を全部置き換えてしまうので、両フックとも最後にリポジトリ固有のフックを同じ引数で呼び直しています。
残る 1 経路と、払う代償
--no-verify は依然として全部を飛ばします。ここはフックの外です。
代償のほうは予想していませんでした。このガードの話をするコミットができません。 検査は文字列の並びしか見ないので、「トレーラーを拒否する理由」を本文に書いたコミットは、トレーラーを付けたコミットと区別がつきません。
ガードのテストスイートも同じ理由で、パターンを断片から組み立てています。
TRAILER="Claude""-Session:"
実行時には元の文字列になりますが、ファイルにもコマンド行にもその並びは現れません。このファイルをシェル経由で書き出すときに要る書き方で、テストを実行するだけなら要りません。回避策と抜け道が同じ操作なので、ここは塞げないままです。