W3docs

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.

Git hooks firing at stages of the commit and push lifecycle

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-commit

A 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-receive and post-receive) run on the remote repository when it receives a push — useful for enforcing policies centrally.

The most commonly used hooks are client-side:

HookFiresTypical use
pre-commitBefore a commit is createdLint and test staged files; abort on failure.
prepare-commit-msgBefore the message editor opensInsert a template or ticket number.
commit-msgAfter the message is writtenEnforce a message convention.
post-commitAfter a commit completesSend a notification; no effect on the commit.
pre-pushBefore a push is sentRun 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
fi

The 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 .githooks

Tools like Husky automate exactly this for JavaScript projects, wiring up shared hooks during install.

Practice

Practice

Which statements about Git hooks are correct?