The Machine That Guards Review Claims in Commit Messages
A bash gate turns free-text Security-review claims into three machine-tested invariants on the commit-msg hook.
TL;DR
Review claims in commit messages are usually free text nobody checks, so one repo added a bash commit-msg gate turning them into machine-tested invariants. The script enforces exactly one Security-review: trailer, a fixed position above Co-Authored-By, and substance, matching "tests only" claims against a whitelist of changed paths. It's bypassable with --no-verify, but it cheaply catches honest mistakes.
I was reading a repository's commit history and three inconsistencies stood out. One commit claimed Security-review: code+security while its diff touched only a documentation file. Another carried the same claim twice inside one message. A third carried no claim at all, even though it modified an authentication module. All three passed without a warning, because review claims in commit messages have always been free text that nobody checks. A new gate in that repository turns the habit into machine-tested invariants, and the lesson travels to any repository whose review claims are still unstructured prose.
From Speech to Invariants
The gate is a bash script that receives the commit message through a file and runs three checks. Placing it on the commit-msg hook is not arbitrary. The Git documentation describes the commit-msg hook as receiving "the name of the file that holds the proposed commit log message" and notes it "can also be used to refuse the commit after inspecting the message file" [4]. The checkpoint sits exactly before the commit exists, and a rejection aborts the commit rather than leaving a mark after the fact.
The first invariant is count: exactly one line starting with Security-review:. Zero means the claim was forgotten; two mean a double claim that confuses readers and can hide a contradiction between two different reviews. The second invariant is position: the trailer sits directly above the Co-Authored-By line, preceded by one blank line. The rule feels rigid, but a consistent format makes trailers queryable; a future audit script can scan history without building a fragile parser.
The third invariant is the interesting one: substance. When the trailer claims "tests only", the script matches every path in git diff --cached against a whitelist of test directories. The Git documentation defines that command as the changes "between the index and your last commit; what you would be committing if you run git commit without -a option" [5]. One path outside the whitelist and the commit is rejected. This is substance validation without NLP: simple path matching that is deterministic and testable. The script even carries its own selftest, so the gate's logic is itself under test.
One naming decision quietly determines the gate's usefulness: the claim is written as a fixed-prefix line in the message body, not as free-form prose inside the description. A line with a fixed shape can be counted, searched, and moved between tools; free prose can only be read. Every invariant above collapses if the format itself is not agreed on first.
Exemptions, Honest Limits, and Adoption
Commits whose every path is non-code, documentation only for instance, skip the trailer requirement. Without that exemption, a pure docs change would be forced to write a vacuous review claim, and forced claims dilute real ones. The exemption keeps the signal sharp.
Two design details earned my respect. First, the repository root can be overridden through an environment variable, so the same gate runs from other scripts or its selftest without being hard-wired to one location; a small detail that separates a one-off script from a tool that survives. Second, this first version reads as an honest foundation: the usage line names subcommands that are not all implemented yet, and two audit-log file paths sit declared in the configuration before they are used. The successor commit adds the next checks. Extending stays safe because the selftest locks current behavior, the same pattern as characterization tests on production code.
The limit is stated openly: Git hooks "can be bypassed with the --no-verify option" [4]. This gate does not stop a determined violator; its job is raising the price of carelessness. Unintentional mistakes are caught at commit time, while the audit trail keeps recording who claimed what over which diff.
Copying this pattern elsewhere does not need a perfect official process. Start from the cheapest invariant, exactly one claim line, then add position and substance once the team is used to it. I see a gate like this as a team contract executed by a machine: humans write the rules, but compliance no longer depends on human memory.
Sources
[4] Git Documentation: githooks, accessed 11 October 2026.
[5] Git Documentation: git-diff, accessed 11 October 2026.