The scope diff tells you which paths changed. It does not tell you whether the reason is credible. For that, ask the agent to review its own diff with a prompt that makes omission hard.
Two research threads justify spending a model call here. Self-reflection improves LLM problem solving: in a study that had agents reflect on incorrect answers and try again, the improvement was significant at p<0.001 across nine models Self-Reflection in LLM Agents. Reflexion turns environment feedback into verbal self-reflection carried into the next attempt and beats prior methods on Python and Rust code writing tasks the Reflexion guide. The practical lesson is that reflection works best when it has something concrete to reflect on, which for us is the staged diff.
Prompt it as a spec, not a chat message. Coding agents fail for structural reasons: missing scope, no stop condition, unverifiable acceptance criteria the agent prompting guide. Compare a chat-style nudge with a spec style instruction, where the latter names the exact files, the constraints, and the output format the agent prompting guide. And remember that a chatbot prompt breaks the moment the model runs in a loop, because the agent will improvise without a stop condition agent prompt engineering. Here is a prompt shaped for the task:
You are reviewing the diff you are about to commit. Do not change any files.
Task scope (allowlist):
{{scope_list}}
Staged files:
{{changed_paths}}
For every staged file, return one JSON object. No prose outside the JSON.
Schema:
{
"reviewed_files": [
{
"path": "<repo-relative path>",
"reason": "<one sentence, concrete: what changed and why this task needs it>",
"in_scope": true | false
}
],
"out_of_scope": ["<paths>"],
"untested_behavior": "<what a reviewer should test, or 'none'>",
"verdict": "approve" | "revise"
}
Rules:
1. List every staged file exactly once.
2. If a file is not on the allowlist, mark in_scope false, even if you think it helps.
3. A reason that only restates the file name is invalid.
Three things make this prompt a gate rather than a formality. The staged file list is injected, so the agent cannot quietly omit a file. The JSON schema enforces one entry per file. The reason field forces a claim that the hook can check for emptiness and that a human can check for plausibility.
The cross-check that closes the omission hole
A model can still return fewer entries than there are staged files, so the hook must reconcile the two sets before trusting the verdict. This is the insight most implementations miss: the self-critique is only as good as the diff it is compared against. Do the reconciliation in code, not in the prompt:
staged = set(subprocess.check_output(
["git", "diff", "--cached", "--name-only"], text=True).splitlines())
review = json.loads(run_agent(review_prompt))
reviewed = {f["path"] for f in review["reviewed_files"]}
missing = staged - reviewed
extra = reviewed - staged
if missing or extra or review["verdict"] != "approve":
print("Self-review failed:", {"missing": missing, "extra": extra})
sys.exit(1)
The extra check catches a subtler failure: an agent listing a file it did not change. That is usually a sign it is describing a plan rather than the diff. Treat both as failures. In practice this reconciliation is what converts the self-critique from a letter of intent into a checkable assertion.
A cheap fallback when the model call is too slow
The rule of thumb for local hooks is worth repeating because it decides the architecture: if pre-commit hooks take more than five seconds, developers will bypass them Git hooks for AI agents, and the same source, in a different article, sets under five seconds as the target for local hooks and moves heavy analysis to CI AI in Git hooks. A model call is unlikely to finish in that budget on every commit.
The resolution is to run gate 2 only when gate 1 finds nothing, or only with an opt-in flag, and always report the gate 1 result in under a second. If you do call a model in the fast path, use a small fast model for classification and keep the context to the staged paths and the scope file. Quanta Inference puts text, image, video, audio and embedding models behind one OpenAI-compatible API key, which makes swapping a fast model in for the pre-commit path a config change rather than an integration project Quanta Inference.