DEV Community

Kitforge
Kitforge

Posted on

5 Git Hooks That Stop AI Coding Agents From Breaking Your Repo

AI coding agents are fast. That is the whole point, and also the whole problem. Claude Code or Cursor can produce ten commits in the time you would normally spend on one, and none of those commits come with the hesitation a human feels before pushing to main.

Rules in a CLAUDE.md file help, but they rely on the model remembering and obeying them. Git hooks are different: they run outside the model, on your machine, and they cannot be talked out of it. Here are the five hooks I install in every repo where an agent has commit access, with working implementations.

All of these live in a .githooks/ directory in the repo, activated once with:

git config core.hooksPath .githooks
Enter fullscreen mode Exit fullscreen mode

That keeps the hooks versioned and shared with the team instead of hidden in .git/hooks.

1. Block direct commits to main

Agents love committing to whatever branch is checked out. If that branch is main, you are one git commit away from an unreviewed change landing in production history.

.githooks/pre-commit:

#!/bin/sh
branch=$(git symbolic-ref --short HEAD 2>/dev/null)
if [ "$branch" = "main" ] || [ "$branch" = "master" ]; then
  echo "Direct commits to $branch are not allowed. Create a feature branch."
  exit 1
fi
Enter fullscreen mode Exit fullscreen mode

The agent hits this once, reads the message, and creates a branch. That is exactly the behavior you want: the correction happens inside the agent's own loop, not in your review comments three hours later.

2. Block force pushes to shared branches

An agent that gets confused about history will sometimes reach for --force. On a shared branch that is unrecoverable damage to everyone else's work.

.githooks/pre-push:

#!/bin/sh
protected="main master develop"
while read local_ref local_sha remote_ref remote_sha; do
  remote_branch=$(echo "$remote_ref" | sed 's|refs/heads/||')
  for p in $protected; do
    if [ "$remote_branch" = "$p" ]; then
      if [ "$remote_sha" != "0000000000000000000000000000000000000000" ]; then
        if ! git merge-base --is-ancestor "$remote_sha" "$local_sha"; then
          echo "Non-fast-forward push to $remote_branch blocked."
          exit 1
        fi
      fi
    fi
  done
done
Enter fullscreen mode Exit fullscreen mode

This blocks any non-fast-forward update to a protected branch, which covers force pushes and history rewrites in one check.

3. Require tests to pass before a commit

The most common agent failure mode is "code looks right, tests never ran." A pre-commit hook that runs the fast test suite makes that impossible to skip silently.

.githooks/pre-commit (append to hook 1):

if [ -f package.json ]; then
  npm test --silent || { echo "Tests failing. Commit blocked."; exit 1; }
elif [ -f pytest.ini ] || [ -f pyproject.toml ]; then
  pytest -x -q || { echo "Tests failing. Commit blocked."; exit 1; }
fi
Enter fullscreen mode Exit fullscreen mode

If your full suite is slow, point this at a smoke subset. The point is not coverage, it is that "commit" and "ran tests" can no longer drift apart.

4. Enforce commit message format

Agents write commit messages like "update files" when they are moving fast. If your repo uses conventional commits (and your changelog tooling depends on them), enforce it at the door.

.githooks/commit-msg:

#!/bin/sh
pattern="^(feat|fix|docs|refactor|test|chore)(\(.+\))?: .{10,}"
if ! grep -qE "$pattern" "$1"; then
  echo "Commit message must be conventional: type(scope): description"
  exit 1
fi
Enter fullscreen mode Exit fullscreen mode

Agents adapt to this instantly because the error message tells them the exact format to retry with.

5. Block secrets at the door

Agents paste example keys, tokens from logs, and credentials from your .env.example into code more often than anyone admits. A cheap pattern check catches the obvious ones.

.githooks/pre-commit (append):

if git diff --cached | grep -qE '(sk-[a-zA-Z0-9]{20,}|AKIA[0-9A-Z]{16}|-----BEGIN (RSA|EC) PRIVATE KEY-----)'; then
  echo "Possible secret in staged changes. Commit blocked."
  exit 1
fi
Enter fullscreen mode Exit fullscreen mode

This is not a replacement for a real scanner like gitleaks, but it catches the three patterns that cause the most pain, in zero dependencies.

The pattern that matters

Notice what these have in common: none of them trust the model. Every rule that lives only in a prompt is a suggestion. Every rule that lives in a hook is physics.

If you do not want to write and tune these yourself, I packaged this exact setup - the hooks, the CLAUDE.md presets, the subagents, and the slash commands - into the Agentic Coding Kit. It unzips into any repo and gives Claude Code or Cursor this discipline from the first prompt. But whether you use my kit or copy the snippets above, put the guardrails in git, not in the prompt.

Top comments (0)