Git Hooks

Git hooks are scripts that run automatically at specific points in Git's workflow, letting you enforce checks like linting or tests before a commit or push is allowed to proceed.

What Hooks Are and Where They Live

Every Git repository has a .git/hooks directory containing sample scripts for every hook Git supports, each ending in .sample so it's inactive by default. To enable a hook, remove the .sample suffix, make the file executable, and write your logic inside it, typically as a shell script, though any executable — Python, Node, a compiled binary — works.

Hooks fall into two broad categories: client-side hooks, which run on your local machine during actions like committing or pushing, and server-side hooks, which run on the remote server when it receives pushed commits. This lesson focuses on client-side hooks, since those are the ones you set up on your own machine.

The pre-commit Hook

The pre-commit hook runs before Git even opens the commit message editor, making it the ideal place to catch problems early: linting errors, formatting issues, accidentally committed secrets, or failing tests on the changed files. If the script exits with a non-zero status, the commit is aborted and nothing is recorded.

A basic pre-commit hook

$ cat .git/hooks/pre-commit
#!/bin/sh
npx eslint --max-warnings=0 $(git diff --cached --name-only --diff-filter=ACM | grep '\.js$')

$ git commit -m "Add new checkout button"
/src/checkout.js
  12:5  error  'total' is never reassigned. Use 'const'

husky - pre-commit hook failed (add --no-verify to bypass)
Note: Anyone can bypass a hook with git commit --no-verify, so client-side hooks are a convenience for catching mistakes early, not a security boundary. Enforce anything critical again in CI.

The pre-push Hook

The pre-push hook runs after commits are made but before they leave your machine, right when you run git push. It's a good place for slower checks that would be annoying on every single commit, like running a full test suite, since it only fires once per push rather than once per commit.

A pre-push hook running tests

$ cat .git/hooks/pre-push
#!/bin/sh
npm test

$ git push origin feature-branch
> npm test
Test Suites: 14 passed, 14 total
Tests:       112 passed, 112 total
Enumerating objects: 9, done.
To github.com:org/project.git
   a1b2c3d..e4f5g6h  feature-branch -> feature-branch

Making Hooks Executable

A hook script that isn't marked executable is silently ignored by Git — no warning, no error, it simply never runs. This is one of the most common reasons a hook "doesn't work" for a new contributor who copied a script but forgot this step.

Enable a hook script

$ chmod +x .git/hooks/pre-commit
$ ls -l .git/hooks/pre-commit
-rwxr-xr-x  1 dev  dev  312 Mar  3 10:02 .git/hooks/pre-commit

Common Client-Side Hooks

HookRuns whenTypical use
pre-commitBefore the commit message is writtenLinting, formatting, secret scanning
commit-msgAfter the commit message is writtenEnforcing a message format or ticket ID
pre-pushBefore commits are sent to the remoteRunning the full test suite
post-checkoutAfter switching branchesReinstalling dependencies if the lockfile changed

Sharing Hooks with Your Team

Since .git/hooks lives inside the local .git directory, it is never committed or shared automatically through cloning. Teams that want consistent hooks across every contributor typically use a tool like Husky, which stores hook scripts in a tracked folder (commonly .husky) and installs them into .git/hooks via a setup step, often triggered automatically by npm install.

  • Raw hooks live only in .git/hooks and are local to your clone by default
  • Tools like Husky or pre-commit (the Python framework) track hook definitions in the repo itself
  • A postinstall or setup script wires the tracked definitions into .git/hooks for every contributor
  • This ensures a new clone gets the same checks without any manual copying
Note: If you're starting fresh, reach for Husky (JavaScript projects) or the pre-commit framework (language-agnostic) rather than hand-writing and distributing raw hook scripts — both handle the sharing problem for you.

Exercise: Git Hooks

What is a Git hook?