Automating Code Checks with Git Pre-Commit Hooks

Every developer knows the frustration of pushing code only to have CI/CD pipelines fail due to formatting issues or linting errors. What if you could catch these problems before they even reach your repository? Git pre-commit hooks offer a powerful solution that runs automated checks locally, saving time and maintaining consistent code quality across your team.
This article walks you through setting up git pre-commit hooks using the pre-commit framework, configuring essential tools like ESLint, Prettier, and Black, and implementing best practices that keep your development workflow smooth and efficient.
Key Takeaways
- Git pre-commit hooks run automated checks before commits, preventing common issues from reaching your repository
- The pre-commit framework simplifies hook management with YAML configuration and language-agnostic support
- Speed optimization and gradual adoption are crucial for team acceptance
- Alternative tools like Husky and Lefthook offer different approaches for specific needs
What Are Git Pre-Commit Hooks?
Git hooks are scripts that Git executes before or after events such as commit, push, and receive. A git pre-commit hook specifically runs after you stage changes but before Git creates the commit. If the hook exits with a non-zero status, Git aborts the commit, giving you a chance to fix issues first.
While you can write hooks as simple shell scripts in .git/hooks/
, managing them becomes complex as your project grows. That’s where the pre-commit framework shines—it provides a standardized way to manage and share hooks across your team.
Why Use the Pre-Commit Framework?
The pre-commit framework solves several pain points with native Git hooks:
- Easy installation and updates: Hooks are versioned and installed via a simple YAML configuration
- Language agnostic: Run Python linters, JavaScript formatters, and shell scripts from one config
- Selective execution: Hooks run only on changed files, keeping commits fast
- Team consistency: Share the same
.pre-commit-config.yaml
file across your entire team
Setting Up Pre-Commit in Your Project
First, install the pre-commit package:
pip install pre-commit
Create a .pre-commit-config.yaml
file in your project root. Here’s a practical configuration that covers common needs:
repos:
# Basic file fixes
- 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
# Python formatting with Black
- repo: https://github.com/psf/black
rev: 24.2.0
hooks:
- id: black
files: \.py$
# Python type checking with Mypy
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0
hooks:
- id: mypy
additional_dependencies: [types-all]
files: \.py$
# JavaScript/TypeScript with Prettier
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.1.0
hooks:
- id: prettier
files: \.(js|jsx|ts|tsx|json|css|md)$
# JavaScript/TypeScript linting with ESLint
- repo: https://github.com/pre-commit/mirrors-eslint
rev: v8.56.0
hooks:
- id: eslint
files: \.(js|jsx|ts|tsx)$
additional_dependencies:
- eslint-config-standard
- eslint-plugin-react
# Run project tests
- repo: local
hooks:
- id: pytest
name: pytest
entry: pytest
language: system
pass_filenames: false
always_run: true
Install the git hooks:
pre-commit install
Now your hooks will run automatically on every commit. To run them manually on all files:
pre-commit run --all-files
Discover how at OpenReplay.com.
Optimizing Hook Performance
Speed matters—slow hooks discourage developers from committing frequently. Follow these practices:
Run hooks only on staged files: The pre-commit framework does this by default, but ensure your custom hooks respect this pattern.
Use fast tools: Choose Ruff over Flake8 for Python, or Biome over ESLint for JavaScript when speed is critical.
Skip expensive checks locally: Reserve comprehensive test suites for CI/CD. Local hooks should focus on quick wins like formatting and basic linting.
Parallelize when possible: Some tools support parallel execution. For example, pytest can run tests in parallel with pytest-xdist
.
Team Adoption Best Practices
Getting your team to adopt pre-commit hooks requires thoughtful implementation:
Start small: Begin with non-intrusive hooks like trailing whitespace removal. Add stricter checks gradually.
Document the setup: Include installation instructions in your README. Make it part of your onboarding process.
Handle environment differences: Use language-agnostic hooks where possible. For language-specific tools, ensure your .pre-commit-config.yaml
specifies compatible versions.
Provide escape hatches: Sometimes developers need to bypass hooks. The --no-verify
flag skips all hooks:
git commit --no-verify -m "Emergency fix"
Use this sparingly and document when it’s appropriate.
Alternative Approaches
While pre-commit is the most popular framework, alternatives exist:
Husky: Popular in JavaScript projects, integrates well with npm scripts.
Native Git hooks: Direct shell scripts in .git/hooks/
offer maximum control but lack portability.
Lefthook: Fast, cross-platform alternative with parallel execution support.
For most teams, pre-commit offers the best balance of features, performance, and ecosystem support.
Common Pitfalls and Solutions
Hook failures on CI: Ensure CI environments have all necessary dependencies installed. Consider running pre-commit run --all-files
as a CI step.
Platform-specific issues: Test hooks on all platforms your team uses. Use Docker for consistent environments if needed.
Merge conflicts in config files: Keep .pre-commit-config.yaml
changes minimal and atomic. Update hook versions in separate commits.
Conclusion
Git pre-commit hooks transform code quality from a reactive process to a proactive one. By catching issues before they enter your repository, you save time, reduce context switching, and maintain higher standards across your codebase. Start with basic formatting hooks, gradually add linters and type checkers, and always prioritize speed to keep your team’s workflow smooth.
The key to successful adoption lies in balancing thoroughness with developer experience. Fast, focused hooks that provide immediate value will become an indispensable part of your team’s workflow.
FAQs
Yes, pre-commit excels at monorepos. You can configure different hooks for different file patterns and directories. Use the files and exclude keys in your configuration to target specific languages or folders within your repository.
Use language version managers like nvm or pyenv alongside pre-commit. Alternatively, specify language_version in your hook configuration or use Docker-based hooks to ensure consistent environments across all developers' machines.
Without pre-commit installed, hooks won't run locally but your code will still commit. To enforce standards, run pre-commit checks in your CI pipeline as a safety net and make pre-commit installation part of your project setup documentation.
Gain control over your UX
See how users are using your site as if you were sitting next to them, learn and iterate faster with OpenReplay. — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.