Lefthook — Fast Polyglot Git Hooks Manager
Overview
Lefthook is a fast git hooks manager written in Go. It runs hooks in parallel, supports filtering by file type, works with any language, and is significantly faster than alternatives like Husky. A single lefthook.yml replaces scattered .git/hooks/ scripts.
When to Use
- Enforcing linting, formatting, and tests before commits
- Running type checks or build verification before push
- Ensuring commit message conventions (conventional commits)
- Polyglot repos where hooks span multiple languages
- Teams that need reliable, fast pre-commit checks
Installation
# macOS
brew install lefthook
# npm (project-local, works in any language repo)
npm install --save-dev @evilmartians/lefthook
# Go
go install github.com/evilmartians/lefthook@latest
# Install git hooks (run once after adding lefthook.yml)
lefthook install
# Verify
lefthook --version
Key Patterns
Basic lefthook.yml
# lefthook.yml
pre-commit:
parallel: true
commands:
lint:
glob: "*.{ts,tsx,js,jsx}"
run: npx eslint {staged_files}
format:
glob: "*.{ts,tsx,js,jsx,css}"
run: npx prettier --check {staged_files}
type-check:
run: npx tsc --noEmit
commit-msg:
commands:
conventional:
run: npx commitlint --edit {1}
pre-push:
commands:
tests:
run: npm test
Running hooks manually
# Run a specific hook
lefthook run pre-commit
# Run with verbose output
lefthook run pre-commit --verbose
# Skip hooks for a commit (emergency)
LEFTHOOK=0 git commit -m "emergency fix"
# Or per-hook skip
git commit -m "wip" --no-verify
Parallel execution with filtering
pre-commit:
parallel: true
commands:
go-lint:
glob: "**/*.go"
run: golangci-lint run {staged_files}
go-test:
glob: "**/*.go"
run: go test ./...
py-lint:
glob: "**/*.py"
run: ruff check {staged_files}
py-format:
glob: "**/*.py"
run: black --check {staged_files}
js-lint:
glob: "**/*.{ts,js}"
run: npx eslint {staged_files}
Scripts (shell scripts as hooks)
# lefthook.yml
pre-commit:
scripts:
"check-secrets.sh":
runner: bash
# .lefthook/pre-commit/check-secrets.sh
#!/bin/bash
if git diff --cached | grep -E "(password|secret|api_key)\s*=" > /dev/null 2>&1; then
echo "Potential secret detected in staged changes!"
exit 1
fi
Skipping on CI
# lefthook.yml
# Automatically skips on CI (LEFTHOOK env var detection)
pre-commit:
skip:
- merge
- rebase
commands:
lint:
run: npx eslint {staged_files}
Per-project local overrides
# lefthook-local.yml (gitignored, developer overrides)
pre-commit:
commands:
lint:
skip: true # Disable lint locally for this developer
Common Pitfalls
- Forgetting
lefthook install: Addinglefthook.ymldoesn't activate hooks — runlefthook installafter changes. {staged_files}vs{all_files}: Use{staged_files}for pre-commit to only check changed files.{all_files}runs on everything.- Slow sequential hooks: Add
parallel: trueto run commands concurrently. - Hook not running: Check that
lefthook installran and that.git/hooks/pre-commitexists and is executable. - Skipping hooks in CI: Set
LEFTHOOK=0in CI env vars if you don't want hooks to run there.
Related Skills
- commitizen-commits — standardized commit message tooling
- eslint-prettier-husky-config — ESLint/Prettier config (Husky alternative)
- git-guardrails-claude-code — git safety for Claude Code
GitNexus Index
domain: developer-tooling
maturity: stable
complexity: low
language: polyglot
config-file: lefthook.yml, lefthook-local.yml
written-in: go
replaces: husky, pre-commit