Tags: web-dev concept

Pre-Commit Hooks

Date: 2026-08-17


Checks that run locally before a commit or push. They’re for fast feedback on the things you’d be embarrassed to push — and every second they add is paid on every commit, which is why most teams eventually make them slower than anyone will tolerate.


A git hook is a script git runs at a point in its workflow. Pre-commit runs before the commit is created; pre-push before a push.

What belongs where

Pre-commitPre-pushCI
Format changed files✓
Lint changed files✓✓
Secret scanning✓✓
Commit message format✓
Type check✓✓
Unit tests✓✓
Full test suite✓
Build✓
E2E✓

The rule: pre-commit gets things that are fast and scoped to changed files. Everything whole-repository belongs later.

The time budget

< 2s     unnoticed
2–5s     tolerable
5–15s    irritating; people start
         using --no-verify
> 15s    universally bypassed

Hooks that get bypassed are worse than no hooks, because the team believes checks are running and they aren’t. Budget five seconds and defend it.

Only changed files

The single technique that keeps it fast:

{
  "lint-staged": {
    "*.{js,ts,tsx}": ["eslint --fix", "prettier --write"],
    "*.{css,md,json}": ["prettier --write"]
  }
}

lint-staged runs each tool only on the staged files, so the cost is proportional to your change rather than to the repository — Linting, Formatting.

The one that’s non-negotiable

Secret scanning. It’s the only check where the pre-commit position is genuinely better than CI:

CI catches it     the secret is already in
                  the remote's history
                  → rotate, and it may
                    already be scraped

PRE-COMMIT        it never leaves the
                  machine

This is the one hook that prevents rather than reports — everything else in this note is a convenience — Secrets Management.

Bypassing

git commit --no-verify

This exists and people use it. Treat that as information rather than as a discipline problem:

  • Occasional bypass — fine. Committing work in progress, or an emergency
  • Habitual bypass — the hooks are too slow, and the fix is to make them faster, not to lecture

Never rely on hooks as the only enforcement. They’re local, skippable and can be uninstalled. CI is the gate; hooks are the fast path — Continuous Integration.

Setup that survives

Hooks live in .git/hooks, which isn’t committed — so they need installing per clone. Tools handle it:

{
  "scripts": { "prepare": "husky" }
}

prepare runs after npm install, so a fresh clone gets hooks with no extra step. A hook nobody has installed is doing nothing, and that’s the common failure — half the team has them and half doesn’t.

Auto-fixing, carefully

lint --fix and prettier --write
  → modify files, then re-stage them

Convenient, and it means the thing you reviewed isn’t quite the thing you committed. Usually harmless for formatting; worth knowing when a lint auto-fix changes behaviour, which occasionally happens.

Some teams prefer check-only hooks — fail and tell you to fix it — for exactly this reason. Both are defensible; the auto-fix version has less friction and is what most teams land on.

What not to put in

  • The full test suite. It belongs in CI, and it will get bypassed
  • Anything network-dependent. An offline developer can’t commit
  • Anything whole-repository. It scales with the codebase, not the change
  • A build. Too slow, and it doesn’t verify what CI will build anyway

The test for any candidate hook: would I still want this if it added four seconds to every commit for the next year?