Blog / claude code
Stop a coding agent from skipping your git hooks
A deny rule for git commit --no-verify misses --no-verify at the end, -n and core.hooksPath. A short PreToolUse hook blocked all of them in our tests.
On this page
- A deny rule catches one spelling
- A PreToolUse hook catches them all
- What it doesn't cover
- Tell the agent what to do instead
- When the agent stops to ask
- Frequently asked questions
- Does Claude Code use git commit --no-verify?
- Why doesn't my deny rule for --no-verify work?
- How do I block --no-verify in Claude Code?
- Will the hook block commit messages that mention --no-verify?
- Try it with your team
Your pre-commit hook runs the linter, the tests or a secret scan. When it fails, an agent that has been told to "get it committed" has an easy way out: git commit --no-verify. The commit lands and the check never ran.
In our test, Claude Code didn't take that way out on its own. We gave it a repo whose pre-commit hook always fails and asked it to commit. It stopped and asked:
I didn't use `git commit --no-verify`, since that bypasses a check your repo set up. Pick one:
- **Skip the hook:** I commit with `--no-verify`.
- **Fix the hook:** you tell me what it should do, or I remove it.That's good behaviour, but it is a choice the agent makes each time, not a rule. If you want the hook to always run, make it a rule. We tested two ways with Claude Code 2.1.318 in claude -p.
A deny rule catches one spelling#
The obvious fix is a deny rule in .claude/:
{
"permissions": {
"deny": ["Bash(git commit --no-verify:*)"]
}
}It matches commands that start with git commit --no-verify. With Bash(git:*) allowed, this is what happened:
| Command | Result |
|---|---|
git commit --no-verify -m 'update a' | blocked |
git commit -m 'update a' --no-verify | committed |
git commit -n -m 'update a' | committed |
git -c core.hooksPath=/ | committed |
Three of four went straight through. -n is the short form of --no-verify, and core.hooksPath points git at an empty folder of hooks. A prefix rule can't list every order of flags.
A PreToolUse hook catches them all#
A Claude Code hook sees the whole command before it runs, so it can look for the flag anywhere. Save this as .claude/ and make it executable (chmod +x). It needs jq.
#!/bin/sh
# Block git commands that skip the repo's hooks.
cmd=$(jq -r '.tool_input.command // ""')
# Drop quoted text (commit messages) before checking the flags.
bare=$(printf '%s' "$cmd" | sed -e "s/'[^']*'//g" -e 's/"[^"]*"//g')
if printf '%s' "$bare" | grep -Eq -- '--no-verify|core\.hooksPath|git( [^;&|]*)? commit( [^;&|]*)? -[a-zA-Z]*n'; then
echo "Blocked: this repo's git hooks must run. Fix what the hook reports instead of skipping it." >&2
exit 2
fi
exit 0Then register it in .claude/:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/no-skip-git-hooks.sh" }
]
}
]
}
}Exit code 2 blocks the command, and the text on stderr goes back to the agent, so it knows why and what to do instead. Any other exit code lets the command run, so a bug in the script fails open; that's why it's worth testing on its own, below. We ran the same three commands that got past the deny rule. None of them committed; Claude Code answered:
The command was blocked before it ran. A PreToolUse hook returned: "Blocked: this repo's git hooks must run. Fix what the hook reports instead of skipping it."We also fed the script commands directly to check it doesn't block too much:
| Command | Hook |
|---|---|
git commit -nm x | blocked |
git commit -am x -n | blocked |
git push --no-verify | blocked |
git commit -m 'fix -n flag' | allowed |
git commit -m "skip --no-verify docs" | allowed |
git log -n 3 | allowed |
npm test -- -n 2 | allowed |
The quoted message is removed before the check, so a commit message that mentions the flag still goes through.
Test yours the same way, without starting the agent:
echo '{"tool_input":{"command":"git commit -n -m x"}}' | .claude/hooks/no-skip-git-hooks.sh; echo "exit $?"Blocked: this repo's git hooks must run. Fix what the hook reports instead of skipping it.
exit 2If the hook does nothing at all, our Claude Code hooks checklist goes through the usual causes.
What it doesn't cover#
- Editing the hook itself. An agent that can edit files can change
.git/hooks/pre-commit. AddEdit(.git/andhooks/**) Write(.git/tohooks/**) deny. We asked Claude Code to overwrite the hook withacceptEditson, and the write was refused: "File is in a directory that is denied by your permission settings". - Other agents. This is a Claude Code hook. Codex and other agents need their own rule. The safest net is running the same checks again where the code is merged, so a commit that skipped them locally is still caught.
- You. It only applies to the agent's commands. You can still run
--no-verifyyourself when you mean to.
Tell the agent what to do instead#
A block works better with a next step. Add one line to CLAUDE.md:
If a git hook fails, fix what it reports and commit again. Never skip hooks; if you can't fix it, stop and say what failed.When the agent stops to ask#
A blocked commit usually means the agent needs a decision from you. Joxo shows an agent's question on your phone and sends your answer back to the session, so a failed hook doesn't sit there until you're back at your desk. See approving agent actions from your phone.
Frequently asked questions#
Does Claude Code use git commit --no-verify?#
In our test it didn't on its own: when the pre-commit hook failed, it stopped and asked whether to skip the hook or fix it. That is a choice it makes each time, so add a hook if you need a rule.
Why doesn't my deny rule for --no-verify work?#
A Bash deny rule matches the start of the command. Bash(git commit --no-verify:*) blocked only that exact order. With the flag at the end, with -n, or with git -c core.hooksPath=/dev/null, the commit went through.
How do I block --no-verify in Claude Code?#
Use a PreToolUse hook on Bash that reads the command and exits 2 when it finds --no-verify, a -n flag on commit, or core.hooksPath. The script and settings are above.
Will the hook block commit messages that mention --no-verify?#
No. The script drops quoted text before it checks, so git commit -m "skip --no-verify docs" is allowed.