Git Hooks

Learn how Git hooks automate checks before commits and pushes, and how tools like Husky make hooks shareable across teams.

What Are Git Hooks?

Git hooks are scripts that run automatically when Git reaches specific events. They help you automate repetitive checks so problems are caught before code is committed or pushed.

Hooks are usually grouped into two categories:

  • client-side hooks, which run on a developer's machine
  • server-side hooks, which run on the Git server when a push is received

Tip: Hooks are best for checks people frequently forget, such as formatting, test commands, or commit message rules.

Client-Side vs Server-Side Hooks

Client-Side Hooks

Client-side hooks run inside a local repository during actions like commit, merge, and push. Common examples include:

  • pre-commit
  • commit-msg
  • pre-push

Server-Side Hooks

Server-side hooks run on a remote repository and can reject pushes or enforce policy. On hosted platforms, CI and branch protection often serve a similar role.

Note: Local hooks improve developer experience, but CI is still the shared source of truth because local hooks can be skipped or may differ across machines.

Three Hooks You Will Use Often

pre-commit

pre-commit runs before the commit is created. If the script exits with a non-zero status, Git stops the commit.

Typical uses:

  • lint checks
  • formatting checks
  • secret scanning
  • basic file validation

Example:

#!/bin/sh
npm run lint
npm run format:check

commit-msg

commit-msg validates the commit message itself. It is the usual place to enforce Conventional Commits.

#!/bin/sh
message_file="$1"

grep -Eq '^(feat|fix|docs|chore): .+' "$message_file" || {
  echo "Commit message must start with feat:, fix:, docs:, or chore:"
  exit 1
}

pre-push

pre-push runs right before Git sends commits to the remote. It is useful for checks that are important but slightly heavier than commit-time checks.

#!/bin/sh
npm test

Where Hooks Live

A repository's built-in hooks live in:

.git/hooks/

Git often creates sample files there, such as pre-commit.sample. To install a hook manually, create a file with the exact hook name and make it executable.

Example:

nano .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit

If the file is not executable, Git ignores it.

Installing Hooks Manually

Here is a small pre-commit hook that runs linting:

cat > .git/hooks/pre-commit <<'HOOK'
#!/bin/sh
npm run lint
HOOK

chmod +x .git/hooks/pre-commit

And a commit-msg hook that blocks non-standard messages:

cat > .git/hooks/commit-msg <<'HOOK'
#!/bin/sh
message_file="$1"

grep -Eq '^(feat|fix|docs|chore): .+' "$message_file" || exit 1
HOOK

chmod +x .git/hooks/commit-msg

The Main Limitation of Raw Hooks

Plain .git/hooks/ scripts are local only. They are not shared automatically when another developer clones the repository. That leads to three common problems:

  • teammates do not get the same hooks
  • hook updates drift over time
  • onboarding becomes manual

Using Husky for Team-Wide Hooks

Husky is a popular solution in JavaScript projects because it stores hook definitions inside the repository.

Basic setup:

npm install --save-dev husky
npx husky init

That creates a .husky/ directory. A typical pre-commit hook might look like this:

#!/bin/sh
. "./_/husky.sh"

npm run lint
npm run format:check

A commit-msg hook can call commitlint:

#!/bin/sh
. "./_/husky.sh"

npx commitlint --edit "$1"

Best Practices

Keep Hooks Fast

Fast hooks get used. Slow hooks get bypassed. Put quick checks in pre-commit and slightly heavier ones in pre-push.

Pair Hooks with CI

Hooks provide local convenience, but CI should still verify important rules centrally.

Write Useful Errors

If a hook blocks work, explain what failed and which command fixes it.

What You Should Remember

Git hooks automate work around commits and pushes. pre-commit is great for lint or format checks, commit-msg enforces message rules, and pre-push can run tests before code is shared. Manual hooks live in .git/hooks/ and need chmod +x, while Husky makes hooks versioned and team-friendly.

Test Your Understanding

Exercise

Exercise 1: Choosing the right hook

Which hook should you use to reject commit messages that do not follow Conventional Commits?

Exercise

Exercise 2: Installing a local hook

What step is required after creating a script in `.git/hooks/pre-commit` so Git can run it?

Exercise

Exercise 3: Sharing hooks with a team

Why do many JavaScript teams use Husky instead of relying only on `.git/hooks/`?

Continue Learning

Explore Related Topics

Related Resources