Git hooks
Learn Git hooks — scripts that run automatically at points in the Git lifecycle to lint, test, and validate. Includes a pre-commit example.
Definition
Git hooks are scripts that Git runs automatically when certain events happen — committing, merging, pushing, and more. They let you plug custom actions into the Git lifecycle: run a linter before each commit, validate a commit message's format, or block a push if the tests fail. Hooks are how teams enforce quality gates locally, before bad code ever leaves a machine.
Where hooks live
Every repository has a .git/hooks directory containing sample scripts with a .sample suffix. To activate a hook, add an executable script with the hook's exact name and no extension:
ls .git/hooks
# pre-commit.sample commit-msg.sample pre-push.sample ...Remove the .sample suffix (or create the file fresh) and make it executable:
chmod +x .git/hooks/pre-commitA hook can be written in any language, as long as the file is executable and starts with an appropriate shebang line.
Client-side vs server-side hooks
- Client-side hooks run on your machine around local operations like committing and pushing. They are great for linting and testing.
- Server-side hooks (such as
pre-receiveandpost-receive) run on the remote repository when it receives a push — useful for enforcing policies centrally.
The most commonly used hooks are client-side:
| Hook | Fires | Typical use |
|---|---|---|
pre-commit | Before a commit is created | Lint and test staged files; abort on failure. |
prepare-commit-msg | Before the message editor opens | Insert a template or ticket number. |
commit-msg | After the message is written | Enforce a message convention. |
post-commit | After a commit completes | Send a notification; no effect on the commit. |
pre-push | Before a push is sent | Run the full test suite as a final gate. |
A pre-commit example
This pre-commit hook runs the linter and blocks the commit if it fails:
#!/bin/sh
npm run lint
if [ $? -ne 0 ]; then
echo "Lint failed — commit aborted."
exit 1
fiThe key mechanism: a non-zero exit code from a pre- hook aborts the operation*. Exit 0 and Git proceeds; exit non-zero and the commit or push is cancelled.
Sharing hooks with a team
Because .git/hooks is not committed, hooks do not travel with a clone. Teams solve this by storing hooks in a tracked directory and pointing Git at it:
git config core.hooksPath .githooksTools like Husky automate exactly this for JavaScript projects, wiring up shared hooks during install.
Practice
Which statements about Git hooks are correct?