Skip to content
Innovialabs

Git Hooks: Automating Checks with pre-commit and Husky

Advanced4 min

In short

Git hooks are scripts Git runs before a commit or push; the pre-commit framework (Python) and Husky with lint-staged (JS) share them across the team and check only the changed files.

The short answer

Git hooks are scripts that Git runs by itself at specific moments: before a commit, when the commit message is written, before a push. If a script exits with an error, the action is cancelled.

The problem with "bare" hooks: they live in .git/hooks, and that folder is not part of the repository. So teams use tools that keep the configuration in code and install the hooks for every developer:

  • pre-commit — a Python framework that works with any language, popular in Python projects.
  • Husky + lint-staged — the standard pairing in JavaScript and TypeScript projects.

Useful client-side hooks

HookWhen it runsWhat is usually checked
pre-commitbefore the commit is createdlinter, formatting, secrets in code
commit-msgafter the message is enteredcommit message format
pre-pushbefore sending to the serverquick tests, type checks

The key principle: a hook must be fast. If a commit takes a minute, developers will start bypassing the checks.

Python: the pre-commit framework

Install the package and create .pre-commit-config.yaml in the project root:

default_install_hook_types: [pre-commit, commit-msg]

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.6.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-added-large-files

  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.6.9
    hooks:
      - id: ruff
        args: [--fix]
      - id: ruff-format

  - repo: https://github.com/compilerla/conventional-pre-commit
    rev: v3.4.0
    hooks:
      - id: conventional-pre-commit
        stages: [commit-msg]

Then:

pip install pre-commit
pre-commit install          # installs hooks into .git/hooks
pre-commit run --all-files  # first run over the whole project
pre-commit autoupdate       # bump the versions in rev

What to know:

  • Each hook runs in its own isolated environment with versions pinned in rev, so the whole team gets identical results.
  • By default only the files in the commit are checked.
  • Here ruff covers both linting and formatting; you can add black, isort, mypy and others.

JavaScript: Husky and lint-staged

Husky manages the hooks, and lint-staged runs commands only on files added to the index.

npm install --save-dev husky lint-staged
npx husky init

husky init creates a .husky folder and adds a prepare script so hooks are installed automatically after npm install. Replace the contents of .husky/pre-commit with:

npx lint-staged

lint-staged configuration in package.json:

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

Checking commit messages with commitlint:

npm install --save-dev @commitlint/cli @commitlint/config-conventional
echo "export default { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js
echo 'npx --no -- commitlint --edit "$1"' > .husky/commit-msg

Now a message like fix: correct discount calculation passes, while fixes does not. The commitlint config format depends on whether the project uses ES modules; check the documentation for your version.

Hooks do not replace CI

Any hook can be skipped with git commit --no-verify, and a new developer may simply not have them installed. So:

  • the same checks are duplicated in CI — that is the last line of defence;
  • hooks give fast feedback before a push, not a guarantee;
  • keep heavy checks (the full test suite, builds) in CI or in pre-push.

Common mistakes

  • Running the linter over the whole project on every commit — slow. Check only changed files.
  • Not pinning tool versions — different people get different results.
  • Turning on strict rules at once in a legacy project — hundreds of errors, and the hooks get disabled. Start with formatting and add rules gradually.
  • Forgetting about Windows: hook scripts must work in Git Bash, so avoid platform-specific commands.

FAQ

Yes, the pre-commit framework is language-agnostic and can run Node tools. But in JS projects Husky and lint-staged feel more natural because they install along with the npm dependencies.

We can help with this

If you'd rather get the result than figure it all out yourself — this is what we do every day.

Discuss a project

Read next

Have an idea or a task? Tell us about it

We'll reply within one business day and propose a solution. The consultation is free.