Set Up Pre-Commit Hooks
How to set up pre-commit hooks with husky, lint-staged, and pre-commit to enforce code quality before commits
Overview
Pre-commit hooks automatically run checks on your code before every commit. They catch linting errors, formatting issues, failing tests, and security vulnerabilities at the earliest possible moment—before they reach CI or production. This recipe covers setting up hooks with the pre-commit framework (Python), husky + lint-staged (JavaScript), and native Git hooks for Java projects.
When to Use
Use this resource when:
- Your team repeatedly commits code that fails CI lint or format checks
- You want to enforce code style without relying solely on PR reviews
- You need to run secrets scanning or vulnerability checks on every commit
- You want fast feedback: fix issues locally instead of waiting for CI to fail
Solution
Python
# Install pre-commit framework
# pip 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-added-large-files
- repo: https://github.com/psf/black
rev: 23.12.1
hooks:
- id: black
language_version: python3.11
- repo: https://github.com/PyCQA/flake8
rev: 7.0.0
hooks:
- id: flake8
args: ['--max-line-length=100']
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.7.1
hooks:
- id: mypy
# Install hooks into .git/hooks/
# pre-commit install
# Run manually on all files
# pre-commit run --all-files
JavaScript
// package.json scripts + husky + lint-staged
// npm install --save-dev husky lint-staged prettier eslint
// package.json
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{json,md,yml}": ["prettier --write"]
},
"scripts": {
"prepare": "husky install",
"lint": "eslint .",
"format": "prettier --write ."
}
}
// .husky/pre-commit (generated by npx husky add .husky/pre-commit)
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged
// Or with the newer husky v9+ syntax:
// echo "npx lint-staged" > .husky/pre-commit
// .lintstagedrc.js
module.exports = {
'*.{js,jsx,ts,tsx}': ['eslint --fix', 'prettier --write'],
'*.{json,md,yaml}': ['prettier --write'],
};
Java
// Java projects typically use Maven or Gradle hooks, not husky.
// Option 1: Maven git hook plugin (com.rudikershaw.gitbuildhook)
// pom.xml:
/*
<plugin>
<groupId>com.rudikershaw.gitbuildhook</groupId>
<artifactId>git-build-hook-maven-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<installHooks>${project.basedir}/git-hooks</installHooks>
</configuration>
</plugin>
*/
// Option 2: Gradle + Spotless + custom Git hook
// build.gradle:
plugins {
id 'com.diffplug.spotless' version '6.23.0'
}
spotless {
java {
googleJavaFormat()
}
}
// git-hooks/pre-commit (chmod +x)
#!/bin/sh
./gradlew spotlessCheck
if [ $? -ne 0 ]; then
echo "Spotless check failed. Run './gradlew spotlessApply' to fix."
exit 1
fi
Explanation
Git hooks are executable scripts in .git/hooks/ that run at specific lifecycle events. The pre-commit hook runs after git commit is invoked but before the commit is created. If the hook exits with a non-zero status, the commit is aborted.
How the tools work:
- pre-commit (Python framework): Manages hook installation and execution across languages. Defined in
.pre-commit-config.yaml. - husky + lint-staged: Husky installs the Git hook; lint-staged filters file paths so only staged files are checked, making commits fast.
- Native Git hooks: Any executable script works. Use Maven/Gradle plugins to distribute hooks across the team.
Trade-offs:
- Hooks add commit latency (seconds to tens of seconds)
- Team members can bypass hooks with
git commit --no-verify - Hooks must be installed per clone; CI is still the ultimate gate
Variants
| Technology | Tooling | Notes |
|---|---|---|
| Python | pre-commit framework | Mature ecosystem; 200+ community hooks available |
| JavaScript / TypeScript | husky + lint-staged | Industry standard for Node.js; fast because only staged files are checked |
| Java | Maven git-build-hook-plugin or Gradle spotless | Run formatters as part of build; hooks call ./gradlew spotlessCheck |
| Go | pre-commit + golangci-lint | Use the pre-commit framework with Go-specific hooks |
| Rust | pre-commit + rustfmt / clippy | Same framework; community hooks available |
| Secrets scanning | gitleaks, trufflehog | Pre-commit hooks prevent API keys and passwords from entering history |
Best Practices
- Keep hooks fast: lint only staged files, not the entire codebase
- Auto-fix when possible: formatters should rewrite files, not just report errors
- Include a
prepareorpostinstallscript so hooks are auto-installed onnpm installorpip install - Run the same checks in CI; hooks are a convenience, not a replacement for CI gates
- Document bypass procedures (
--no-verify) for emergencies, but require PR review when used
Common Mistakes
- Checking the entire repo on every commit — lint-staged and pre-commit’s
filesfilter ensure only changed files are checked - Not auto-installing hooks — new clones skip hooks unless a
preparescript installs them - Conflicting formatters — ensure Prettier and ESLint rules agree; use
eslint-config-prettierto disable conflicting ESLint format rules - Hooks that modify files but don’t re-stage — if a hook reformats code, it must add the file back to the index or the commit will use the old version
- Relying only on hooks — developers can use
--no-verify; CI must enforce the same rules
Frequently Asked Questions
Can I skip hooks for a specific commit?
Yes: git commit --no-verify (or -n). Use this sparingly and always follow up with a cleanup commit. Some teams require manager approval for --no-verify usage.
Should I run tests in pre-commit hooks?
Unit tests: sometimes, if they complete in under 10 seconds. Integration or E2E tests: never; they belong in CI. Slow hooks train developers to bypass them.
How do I share hooks across my team?
Use pre-commit (cross-language) or husky (Node.js). Both store hook configuration in the repo. For Java, use a Maven/Gradle plugin that installs hooks from a tracked git-hooks/ directory during build. Never commit files directly to .git/hooks/—that directory is not tracked.
Related Resources
Bug Report Template
A structured bug report template to help teams reproduce, triage, and resolve defects faster with clear reproduction steps and expected behavior.
DocChangelog Template
A structured changelog template following Keep a Changelog conventions for tracking project releases.
DocCode of Conduct Template
A community code of conduct template to establish inclusive, respectful collaboration standards.
DocContributing Guide Template
A ready-to-use template for open-source and internal project contribution guidelines.
DocDisaster Recovery Plan Template
A disaster recovery plan template for documenting RTO/RPO targets, failover procedures, and recovery runbooks that minimize downtime during catastrophic failures.