Stop AI agents committing to main: a 2-line git hook
Branch protection blocks the push, not the local commit. A pre-commit hook stops any agent from landing work on main, with a one-line escape.

A local pre-commit hook stops agents committing to main. Drop a pre-commit file in .git/hooks, read the current branch, exit non-zero when it is main. The commit aborts before it lands. You still pass --no-verify when you mean it. It runs on every commit, agent or human, with no remote config.
I learned this the hard way on a Friday. Claude Code was deep in a refactor, asked to commit, and committed straight to main. Not a feature branch. Main. The working tree was already rewritten by the time I looked up.
Why GitHub branch protection does not catch this
Branch protection runs on the server. It rejects the push, not the commit. By the time GitHub says no, the agent has already made the commit object on your machine and moved your local main forward.
So the protection you set up in the repo settings fires too late. The agent rewrote your working tree, made the commit, and you are the one who runs git reset to undo it. The guard you want sits earlier, before the commit object exists.
The hook, the two lines that matter
A pre-commit hook is a script git runs before it writes the commit object. The git docs are clear on the contract: exiting with a non-zero status "causes the git commit command to abort before creating a commit". So you read the branch name and exit 1 when it is main.
#!/bin/sh
branch=$(git rev-parse --abbrev-ref HEAD)
if [ "$branch" = "main" ]; then
echo "Blocked: no direct commits to main. Use a branch, or --no-verify to override."
exit 1
fiThe two load-bearing lines are the git rev-parse that gets the current branch and the exit 1 that stops the commit. The rest is plumbing. Swap main for master if that is your default, or test both.
Install it in one repo
Save the script as .git/hooks/pre-commit and make it executable.
chmod +x .git/hooks/pre-commitThat is it. Commit on main now and it aborts before git writes anything.
git commit -m "add auth module"Blocked: no direct commits to main. Use a branch, or --no-verify to override.Git exits 1 and the commit never lands. Your staged changes stay staged, so you switch to a branch and commit again with nothing lost. The hook lives in .git/, so it is local to this clone and does not travel with the repo.
Install it everywhere with a git template
A per-repo hook protects one clone. To get it on every repo you create or clone from now on, point git at a template directory.
git config --global init.templateDir ~/.git-template
mkdir -p ~/.git-template/hooks
cp .git/hooks/pre-commit ~/.git-template/hooks/
chmod +x ~/.git-template/hooks/pre-commitNow git init and git clone copy the hook into the new repo's .git/hooks for you. For repos you already have, run git init inside them again to pull the template in without touching history.
The escape hatch and when to use it
The deliberate override is --no-verify.
git commit --no-verify -m "hotfix on main"That flag skips the hook entirely. Use it when you want a commit on main, a one-line fix or a docs typo where a branch is overkill. The point is that the override is yours and explicit, not the agent's default.
What this does not solve
The hook is a guardrail, not a sandbox. An agent that knows about --no-verify can pass it and walk right past the block. Nothing about a local hook stops a tool from bypassing the local hook.
So treat this as the cheap layer. It catches the ordinary case, the agent committing to main because nobody told it not to. For real isolation, give the agent its own branch from the start with a git worktree. The hook stops the accident. The worktree stops the agent from ever standing on main in the first place.
Newsletter
A short weekly email about AI tools and what's worth trying.
Free. No spam. Unsubscribe anytime.
More like this
All articles →
Claude Code agent loop: 3 causes and the fix
Claude Code looping on the same edit means one of three things: lost context, a failing command, or a vague task. Here is the fix for each.

Cut your Claude Code cost in half with one routing rule
Most of a Claude Code bill is Opus doing work Sonnet handles fine. Here is the routing rule that fixes it, plus how to confirm caching works.

Claude Code sub-agents: when to spawn one (3 cases)
A sub-agent runs in its own context window and hands back a summary. Here is the one rule for when to spawn one, with three worked examples.

Codex CLI review: what beats Claude Code after 2 months
Two months of daily Codex CLI use. The three things that stuck, where it beats Claude Code, and where Claude Code still wins.
Was this helpful?
