git hooks: why a commit will not go through

Updated July 31, 2026

You type git commit, and instead of a new commit the terminal shows a message from a linter or a check, and no commit is made. A hook fired - a script that git runs by itself on a certain event.

There are many events: before a commit is created, after it, before git push, before git rebase. The scripts live in the .git/hooks directory inside the repository, one file per event. If the file is there and executable, git runs it; when the script exits with a non-zero code, git cancels the operation.

Here is how it looks. A pre-commit hook runs the tests and refuses the commit while they fail:

$ git commit -m "add search"
tests failed, commit aborted

Nothing follows. There is no line about a new commit, and the staged changes stay in the index, ready for the next attempt. The text tests failed, commit aborted was printed by the script itself; git added nothing of its own - it just did not create the commit.

The next most common hook is commit-msg. It receives the file with your message and decides whether it passes. For example, it can require a prefix, as in the commit message:

$ git commit -m "update app"
commit message must start with feat:, fix:, docs: or chore:

The difference between the two: pre-commit looks at the changes themselves (formatting, tests, a forgotten console.log), while commit-msg looks only at the message text.

Hooks do not travel with the commit

This is what trips people up most. The .git/hooks directory is part of the service folder .git, and git does not version its contents or send them to the server. Your hooks stay on your machine; a colleague will not have them, even after cloning the same repository. That is why teams distribute hooks with a separate tool: pre-commit in Python projects, husky in Node projects. The tool drops the scripts into .git/hooks on install, while its config sits in the repository and reaches everyone.

Read it, and bypass it if you must

First, read the output top to bottom: it tells you what the hook did not like - a file format, a message length, a failing test. Often the hook fixes the problem for you (reformats a file) and asks you to run git commit again.

You can skip the checks with --no-verify:

$ git commit --no-verify -m "fix: urgent patch"

It skips pre-commit and commit-msg entirely. This is an emergency exit, not a habit: the check was put there for a reason, and a commit that would fail it locally will hit the same check on CI anyway, in front of the whole team.

Common mistakes

Assuming that because a hook fired for you, everyone has it. Hooks are local; without a shared tool to distribute them, each person has their own set, and "it passes for me" guarantees nothing.

Getting used to --no-verify. One bypass in an emergency is fine. Doing it every time means the check is in your way, and the fix is the check or your code, not silencing it with a flag.

Where this is in the book

Chapter 7. How teams actually use git. Nearby you will want git commit and the commit message.