Git Pre-Commit Hooks That Small Teams Actually Keep Using
A teammate pushes a commit with a stray console.log , a 40 MB video file and an AWS key in a config file. The reviewer catches the log line, misses the key, and the video sits in the repository history forever.

A teammate pushes a commit with a stray console.log, a 40 MB video file and an AWS key in a config file. The reviewer catches the log line, misses the key, and the video sits in the repository history forever. Git pre-commit hooks exist to stop exactly this kind of afternoon, and on a small team they are one of the cheapest safety nets you can set up.
I have rolled out hooks on four teams between three and twelve people. Two of those rollouts stuck. The other two were quietly bypassed within a month. The difference had very little to do with which tool we picked and a lot to do with how slow, how strict and how shared the hooks were. Here is what I would do again, and what I would skip.
What Git Pre-Commit Hooks Actually Do
A Git hook is a script that Git runs at a fixed moment. The pre-commit hook runs after you type git commit and before the commit is written. If the script exits with a non-zero status, the commit is refused and the developer sees the output.
That is the whole mechanism. Everything else, from formatters to secret scanners, is just a program the hook calls. The hook lives in .git/hooks/pre-commit, which matters for one reason: the .git folder is not versioned. A hook you write there exists only on your machine. This single detail explains most failed rollouts.
The other hooks worth knowing
- commit-msg checks the message itself, useful if you enforce a ticket number.
- pre-push runs before code leaves the laptop, a better home for anything that takes more than a few seconds.
- post-checkout can remind people to reinstall dependencies after switching branches.
Sharing Hooks So Everyone Runs the Same Checks
Because hooks are not committed, you need a way to distribute them. There are three common routes, and the right one depends mostly on what language your repository already speaks.
| Approach | Fits best | Setup cost |
|---|---|---|
| core.hooksPath pointing at a versioned folder | Any repo, no extra tooling | One command per clone |
| The pre-commit framework (Python) | Polyglot repos, many linters | A YAML file and one install |
| Husky with lint-staged (Node) | JavaScript and TypeScript projects | Runs on npm install |
The simplest option is underrated. Put scripts in a .githooks folder, commit it, and ask each developer to run git config core.hooksPath .githooks once. Add that line to your setup script so nobody has to remember. If your onboarding already struggles with machines that drift apart, read our piece on how a local setup slowly drifts away from production, because hooks suffer from the same problem.
The Five Checks That Earn Their Place
A pre-commit hook should catch mistakes that are cheap to detect and expensive to fix later. On every team I have worked with, these five paid for themselves within weeks:
- Secret scanning. Tools like gitleaks or detect-secrets look for patterns such as private keys and tokens. A leaked key in history means rotation, which is painful, as we covered in why rotating API keys trips up small teams.
- Large file block. Refuse anything over, say, 5 MB. One bad commit can add hundreds of megabytes to every future clone.
- Formatting on staged files. Prettier, Black or gofmt, applied only to what is staged, ends the whitespace debates in review.
- Merge conflict markers. A one-line grep for leftover conflict markers saves an embarrassing broken build.
- Trailing whitespace and line endings. Boring, but mixed CRLF and LF files cause noisy diffs on teams that mix Windows and macOS.
Why Running the Full Test Suite on Commit Is a Mistake
The most common advice I hear is to run every test and the full linter on each commit, so broken code can never enter the repository. It sounds responsible. In practice it is the fastest way to teach a team to type the no-verify flag on git commit.
Developers commit often, sometimes every few minutes, and often with half-finished work on a feature branch. If a commit takes 90 seconds, people stop committing small, which makes history worse, or they bypass the hook, which makes the hook pointless. My rule is blunt: the pre-commit hook must finish in under five seconds on a typical change.
Tests belong in two other places. Fast unit tests can go in a pre-push hook, where a 30 second wait is tolerable because it happens a few times a day. The full suite belongs in continuous integration, which runs on clean machines and cannot be skipped by a tired developer at 6 pm. Hooks are a convenience layer; CI is the gate.
Keeping Hooks Fast on Large Repositories
Speed comes from doing less work, not from faster machines. Three habits keep hooks quick even on repositories with thousands of files.
Only check staged files
Ask git diff with its cached and name-only options for the list of added, copied or modified files, and pass only those to your tools. lint-staged and the pre-commit framework do this for you. Running a linter across the whole tree on every commit is where most slow hooks come from.
Pin tool versions
If one developer has Prettier 2 and another has Prettier 3, the hook will reformat the same file back and forth. The pre-commit framework pins versions in its config file, and Node projects can pin in package.json. Either way, the version should live in the repository.
Cache aggressively
The first run of the pre-commit framework downloads environments and can take a minute. After that it is cached. Warn new joiners so they do not decide the hook is slow on day one.
Rolling Out Hooks Without a Revolt
The two rollouts that failed for me had the same shape: someone added ten strict checks on a Friday, and on Monday every commit failed on files nobody had touched in years. Here is the order that worked instead.
- Week one, add formatting and the large file block only, and run the formatter once across the whole repository in a single commit so later diffs stay clean.
- Week two, add secret scanning in warning mode, review what it flags, and build an allowlist for false positives such as test fixtures.
- Week three, switch secret scanning to blocking and add the conflict marker check.
- Write a short section in the README explaining each check and how to fix a failure in one command.
Accept that the no-verify flag will exist. You cannot stop it locally, and you should not try. What you can do is make CI run the same checks, so a bypass only delays the feedback rather than removing it.
Frequently Asked Questions
Can a pre-commit hook be enforced on every developer?
No. Hooks run on the developer's machine and can always be skipped. Treat them as a fast early warning and repeat the important checks in CI, where they cannot be bypassed.
Do hooks work the same on Windows?
Mostly. Git for Windows runs hooks through its bundled shell, so plain shell scripts work. Watch line endings: a hook saved with CRLF endings can fail with a confusing "not found" error. Set the hooks folder to LF in .gitattributes.
What should happen when the formatter changes a file?
Most tools fix the file and fail the commit, so you can review the change and commit again. Some teams prefer auto-staging the fix. I would avoid that at first, because silent changes confuse people who are new to the setup.
Start With Two Checks This Week
If you do one thing, add a versioned hooks folder with a large file block and a secret scanner, keep it under five seconds, and mirror both in CI. That covers the two mistakes that are hardest to undo. Once the team trusts it, add formatting. More ideas for small team tooling live in our dev tools section.
More in Dev Tools
Dev Tools
How to Set Up direnv for Per Project Environment Variables
You switch from one project to another and the API still points at the wrong database, because an environment variable from the...
Dev Tools
Local HTTPS Development With mkcert in Under Ten Minutes
Your app works on plain http://localhost:3000, then breaks the moment it touches a secure cookie, a service worker or an OAuth...
Dev Tools
Makefile Task Runner Setup for Small Projects
Open the README of almost any small project and you will find a block of seven or eight shell commands under a heading like...