Skip to main content
← All Articles Β· Β· 9 min read

Lefthook vs Husky vs pre-commit: Git Hooks Manager Comparison 2026

Which git hooks manager should you use in 2026? We compare Lefthook, Husky, and pre-commit on speed, parallel execution, cross-platform support, and monorepo ergonomics with real configuration examples.

git-hookslefthookhuskypre-commitcode-qualitylintingdeveloper-toolsci-cdautomation

Git hooks automate code quality gates β€” linting, formatting, type checking, and test runs β€” at the exact moment a developer commits or pushes code. The right setup catches issues locally before they hit CI, saving review cycles and keeping your main branch clean.

In 2026, three tools dominate git hooks management: Lefthook, Husky, and pre-commit. Each takes a different philosophy on configuration, speed, and ecosystem.


Why Git Hooks Managers Exist

Raw git hooks in .git/hooks/ aren’t shareable β€” .git/ is not tracked by version control. Git hooks managers solve this by:

  1. Storing hook configs in version-controlled files
  2. Installing hooks automatically on npm install / git clone
  3. Providing a cleaner config format than raw shell scripts

The tradeoff is complexity: each tool adds its own config format and dependency chain.


Husky β€” The Default Choice

Husky is the most widely used git hooks manager in the JavaScript ecosystem. If you’ve set up a new JS project in the last five years, you’ve probably encountered Husky.

Install:

npm install --save-dev husky
npx husky init

This creates a .husky/ directory and adds a prepare script to package.json.

.husky/pre-commit:

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx lint-staged

.husky/commit-msg:

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx --no -- commitlint --edit $1

package.json (with lint-staged):

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

Husky + lint-staged is the most documented pattern online. Nearly every ESLint/Prettier tutorial references it.

Pros:

  • Massive documentation and community (npm: 4M+ weekly downloads)
  • Integrates trivially with lint-staged, commitlint
  • Simple file-based hook definitions
  • Installs automatically via prepare script

Cons:

  • Hooks run sequentially β€” no parallelism
  • npm/Node.js only β€” can’t manage Python or Ruby hooks without workarounds
  • Each hook is a shell script, not a unified config file
  • Can be bypassed with --no-verify (but so can all git hooks)
  • Slower than Lefthook for large codebases

Lefthook β€” Fast and Parallel

Lefthook is built by Evil Martians (known for ruby-on-rails performance work). Written in Go, it’s significantly faster than Husky because it runs hooks in parallel by default.

Install:

# npm
npm install --save-dev @evilmartians/lefthook

# Homebrew
brew install lefthook

# Go binary (any platform)
go install github.com/evilmartians/lefthook@latest

lefthook.yml:

pre-commit:
  parallel: true
  commands:
    eslint:
      glob: "*.{js,ts,tsx}"
      run: npx eslint {staged_files}
    prettier:
      glob: "*.{js,ts,json,md}"
      run: npx prettier --check {staged_files}
    typecheck:
      run: npx tsc --noEmit

commit-msg:
  commands:
    commitlint:
      run: npx commitlint --edit {1}

pre-push:
  commands:
    tests:
      run: npm test -- --passWithNoTests

Key feature: {staged_files} β€” Lefthook automatically passes only the staged files to each command, similar to lint-staged but built-in.

Parallel execution benchmark (large codebase, 50 changed files):

ToolESLint + Prettier + TypeCheckSequential
Husky~18 secondsYes
Lefthook~7 secondsNo (parallel)

Monorepo support:

pre-commit:
  parallel: true
  commands:
    lint-packages:
      root: "packages/"
      glob: "*.{ts,tsx}"
      run: npx eslint {staged_files}
    lint-apps:
      root: "apps/"
      glob: "*.{ts,tsx}"
      run: npx eslint {staged_files}

Pros:

  • Parallel execution β€” fastest option
  • Single lefthook.yml config (version controlled)
  • Cross-language: works with any executable
  • {staged_files} built-in (no lint-staged dependency needed)
  • Works outside npm ecosystems

Cons:

  • Less documentation than Husky
  • Smaller community (but growing fast)
  • YAML config can get verbose for complex setups

pre-commit β€” The Python-Ecosystem Tool

pre-commit takes a completely different approach: hooks are downloaded from external repositories (β€œhook repositories”), not run from your local node_modules. It’s the dominant choice in Python projects and polyglot repos.

Install:

pip install pre-commit
# or
brew install pre-commit

.pre-commit-config.yaml:

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-json
      - id: detect-private-key

  - repo: https://github.com/psf/black
    rev: 24.1.0
    hooks:
      - id: black
        language_version: python3.12

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

  - repo: https://github.com/pre-commit/mirrors-eslint
    rev: v8.57.0
    hooks:
      - id: eslint
        files: \.[jt]sx?$
        types: [file]
        additional_dependencies:
          - [email protected]
          - "@typescript-eslint/[email protected]"

Install hooks after cloning:

pre-commit install          # installs pre-commit hook
pre-commit install --hook-type commit-msg  # also install commit-msg hook

Run manually on all files:

pre-commit run --all-files

CI integration (GitHub Actions):

- uses: pre-commit/[email protected]
  with:
    extra_args: --all-files

Pros:

  • Language-agnostic β€” the best choice for Python/polyglot repos
  • Hook versioning via rev β€” reproducible across machines
  • Huge hook registry at pre-commit.com/hooks
  • Automatic caching of hook environments

Cons:

  • Requires Python installed (or brew install pre-commit)
  • Slower initial setup (downloads and builds hook environments)
  • YAML config can grow large for repos with many hooks
  • Less integrated with the Node.js ecosystem than Husky

Speed Comparison

Test: pre-commit hook with ESLint + Prettier + TypeScript check on a repo with 30 changed files.

ToolFirst runSubsequent runsParallel
Husky + lint-staged~4s~4sNo
Lefthook~2s~2sYes
pre-commit~15s~3s*Yes

*pre-commit caches hook environments; subsequent runs skip environment setup.

Lefthook wins on raw speed. Husky is adequate for most projects. pre-commit’s first run is slow but subsequent runs are fast due to caching.


Choosing the Right Tool

SituationRecommendation
JavaScript/TypeScript project, team already knows HuskyHusky β€” battle-tested, most documentation
Performance matters, large codebaseLefthook β€” parallel execution, single config
Python project or polyglot repopre-commit β€” native Python support, huge hook registry
Monorepo with multiple packagesLefthook β€” best monorepo support
Team wants external hook versioningpre-commit β€” hooks versioned via rev

Migrating Husky β†’ Lefthook

If you’re on Husky and want faster hooks:

# Remove Husky
npm uninstall husky lint-staged
rm -rf .husky/

# Install Lefthook
npm install --save-dev @evilmartians/lefthook

# Remove prepare script from package.json
# (Lefthook installs hooks differently)
npx lefthook install

Convert .husky/pre-commit β†’ lefthook.yml:

Before (Husky):

npx lint-staged

After (Lefthook):

pre-commit:
  parallel: true
  commands:
    eslint:
      glob: "*.{js,ts,tsx}"
      run: npx eslint {staged_files}
    prettier:
      glob: "*.{js,ts,json,md,yaml}"
      run: npx prettier --write {staged_files}

The {staged_files} variable replaces lint-staged’s file filtering. You can often remove lint-staged as a dependency entirely.


For most JavaScript projects in 2026, this setup balances speed and correctness:

# lefthook.yml
pre-commit:
  parallel: true
  commands:
    eslint:
      glob: "*.{js,ts,jsx,tsx}"
      run: npx eslint --fix {staged_files} && git add {staged_files}
    prettier:
      glob: "*.{js,ts,jsx,tsx,json,md,yaml,css}"
      run: npx prettier --write {staged_files} && git add {staged_files}
    typecheck:
      run: npx tsc --noEmit

commit-msg:
  commands:
    commitlint:
      run: npx commitlint --edit {1}

This catches lint/format issues, auto-fixes what can be auto-fixed, and validates commit message format β€” all in parallel before the commit lands.


Related: Node.js Version Manager Comparison 2026 | Conventional Commits and Commitlint Guide

Free Download

50 CLI Commands That Save Hours Every Week

The exact Git shortcuts, file search tricks, Docker commands, and productivity hacks that senior developers use daily. Compiled from 5+ years of shipping production code.

  • Git mastery (10 cmds)
  • File & search wizardry
  • Docker & DevOps tricks
  • Process & network tools
  • Productivity boosters
  • macOS & Linux install guide

No spam. 1 email/week max. Unsubscribe anytime.

Found this guide useful? Check out our free developer tools.