Fuller University  ·  School of Computing Friday, August 7, 2026
Tooling

The pre-commit setup that survives contact with a real team

Hooks die when they are slow, noisy or preachy. A staged-files setup that stays under two seconds and does not get skipped by week three.

Every pre-commit hook starts the same way: someone adds it, the first week is great, and by week three half the team is typing --no-verify out of muscle memory. The hook did not fail because hooks are a bad idea. It failed because it was slow, noisy, or both. Here is a setup that has survived several years and several teams, along with the reasoning behind each decision — because the reasoning is the part that transfers.

Three rules before any configuration

Under two seconds. Past that, people notice waiting and start avoiding it. This is not a target to aim for; it is a constraint that decides what may run at all. Only fix what can be fixed automatically. Formatting, import order, trailing whitespace. If the hook prints a complaint a human must act on, it interrupted a commit to assign homework — that belongs in CI, where it does not block a train of thought. Only touch staged files. A hook that lints the whole repository punishes you for a one-line change in someone else's mess.

Step one: the hook manager

Git hooks live in .git/hooks, which is not committed, so a manager is needed to make them travel with the project. Any of the popular ones will do. The part that matters is that installation is automatic on npm install, because a hook that requires a setup step is a hook half your team does not have.
npm install --save-dev husky lint-staged
npx husky init
That leaves a single line in the generated hook file:
npx lint-staged

Step two: scope the work to staged files

This is the whole trick. The runner receives only the paths git has staged, so cost scales with the size of your change rather than the size of the repository.
{
  "lint-staged": {
    "*.{js,ts,jsx,tsx}": ["eslint --fix", "prettier --write"],
    "*.{json,md,css}": ["prettier --write"]
  }
}
Both commands write. Neither reports. A commit either goes through with tidier files or fails because the code does not parse — and a syntax error is the one interruption nobody resents.

Step three: put the arguing in a config file

Formatting debates cost more than formatting ever will. Take the defaults, write them down, and move on:
{
  "singleQuote": true,
  "semi": true,
  "printWidth": 100
}
The specific values are unimportant. What matters is that they are in the repository, so nobody's editor is quietly right.

Step four: move the slow, opinionated checks to CI

Type checking, tests, and the full lint pass belong on the server. They are too slow for a commit and too important to skip, which sounds like a contradiction and is really just a scheduling problem. Run them on push and on pull request. Let the hook keep the diff clean; let CI keep the branch correct. The division that works in practice:
  • Pre-commit: format staged files, fix trivial lint, fail on unparseable code.
  • Pre-push (optional): the tests covering changed files, if that is fast in your project.
  • CI: full lint, type check, full test suite, build.

What we removed, and why

Two things get added to hooks constantly and should not be. Commit message linting. It rejects work that is already done, over a rule the author cannot see while typing. If your release tooling needs structured messages, generate them at merge time from the pull request title, where one person edits one string. The full test suite. Even a fast suite drifts past the two-second budget within a few months, and the failure mode is not people fixing tests — it is people skipping hooks. Once someone has learned --no-verify, they use it for everything.

Leave the escape hatch open

Do not fight --no-verify. Someone mid-incident committing a debugging change at two in the morning has better information than your configuration file does. The hook is there to save people effort, not to hold a gate. Which is the honest summary of the whole approach: a good pre-commit hook is a small, fast, silent thing that makes diffs cleaner and never once makes a developer think about it. If yours is being discussed in retrospectives, it is doing too much.

The Weekly Module

One email. Every Friday. Just JavaScript.

The week in the language, the runtimes, and the registry — read by 42,000 developers who’d rather be shipping.