The pre-commit Playbook
pre-commit is a Big Part of the Caparra DevOps Playbook
First Principles: Shift Left and Ship Better Code
One of the core ideas behind DevOps is "shifting left." That means moving critical checks, testing, and feedback earlier in the software development lifecycle. Instead of discovering broken code, style violations, or security risks after deployment (or even in CI), we want to catch them as early as possible... ideally, before the code ever leaves the developer's machine.
Enter pre-commit. This tool takes that "shift left" principle and gives it teeth. By running automated checks before code gets committed to version control, it helps teams enforce consistency, spot bugs early, and maintain high standards without nagging or micromanagement. It’s a practical way to bake quality into your process, one commit at a time.
What Is pre-commit and Why Should You Care?
If you haven’t read our earlier post, here’s the TL;DR: pre-commit is a tool that runs scripts when you try to make a Git commit. It blocks the commit if any of those scripts fail.
That might sound strict, but it’s actually a superpower. Instead of fixing formatting, typos, or secrets after they hit the main branch, you catch them immediately. It creates fast feedback loops and enforces team-wide standards in a way that scales.
How pre-commit Works Under the Hood
pre-commit hooks are just scripts. When you install the tool in your Git repo, it creates a .git/hooks/pre-commit
script that runs a series of commands defined in a .pre-commit-config.yaml
file.
Each hook in that file has a repo (like https://github.com/psf/black
) and a list of hooks to run. Here’s a tiny example:
repos:
- repo: https://github.com/psf/black
rev: 23.3.0
hooks:
- id: black
You install pre-commit with pip install pre-commit
, then activate it with pre-commit install
. After that, it runs every time you make a commit.
Using pre-commit on Your Local Machine (Without Going Crazy)
One of the challenges with pre-commit is juggling different configs across projects. Some use black
, others use ruff
. Maybe you’re hopping between Node, Python, and Terraform. Here’s how to manage that like a pro:
Use virtual environments per project. With Python, use
python -m venv .venv
, or a tool likepoetry
oruv
. Install pre-commit locally with your dev dependencies.Activate the environment before development. Automate this with
direnv
,Makefile
, or adev.sh
script.Use
pre-commit install
to hook into Git. This adds a script to.git/hooks/pre-commit
that runs your configured checks.Optional: install pre-commit globally too.
pipx install pre-commit
lets you use it across systems, but your projects should still define their own configs.
Bonus: If you're using Docker for your dev environment, add pre-commit to your Dockerfile or bind-mount your .git
directory so hooks run inside containers too.
Must-Have Hooks for Every Team
Some hooks are nearly universal:
trailing-whitespace
: removes invisible problems that trigger diff noiseend-of-file-fixer
: adds a newline to avoid subtle issues in POSIX systemscheck-merge-conflict
: makes sure you didn’t leave behind<<<<<<< HEAD
detect-aws-credentials
: stops you from leaking secrets
Language-specific faves:
Python:
black
,ruff
,isort
JavaScript:
eslint
,prettier
Go:
golangci-lint
Check out the full list at pre-commit.com/hooks.html.
Writing Your Own pre-commit Hooks
Sometimes you need a hook that doesn’t exist yet. Here’s an example of a pre-commit hook you could use to check whether a commit message meets your team's formatting rules (like starting with a capital letter, following Conventional Commits, or avoiding vague messages like "fix"):
repos:
- repo: local
hooks:
- id: check-commit-msg
name: Check Commit Message Format
entry: ./scripts/check-commit-msg.sh
language: script
stages: [commit-msg]
The entry
can point to any shell, Python, or Node script. Just make sure it exits with code 0
for pass and non-zero for fail.
Good custom hooks:
Enforce conventional commits
Ban TODOs or print statements
Ensure migrations are up to date
Making pre-commit Work in Teams
Here’s how to roll it out without revolt:
Commit the config. This makes it easy to share the rules.
Run
pre-commit autoupdate
regularly. This updates pinned versions.Use
pre-commit run --all-files
before big merges.Document it. Put setup instructions in
README.md
or aCONTRIBUTING.md
.
Common Pitfalls and How to Avoid Them
Slow hooks? Profile them. Some linters can run on only changed files.
Unintended file skips? Use
files:
andexclude:
to tune scope.Conflicts with IDEs? Align formatter config (like
black
andprettier
) with your editor.Different OS behavior? Normalize line endings with
check-ast
,.editorconfig
, and Docker.
Caparra and pre-commit
Here at Caparra, we're big fans of pre-commit. It's a simple but effective way to define your team's engineering standards. The .pre-commit-config.yaml file format is easy for both humans and AI agents to read, and a springboard for enforcing your standards across the organization.
Putting your engineering standards in place with pre-commit is a great way to help your team write more consistent code. Doing so has a big return on your investment: new team members will onboard more quickly, and your team will save hours of time on code reviews.
Final Thoughts: pre-commit as a Team Habits Engine
pre-commit doesn’t just stop bad code. It builds good habits. It reduces context-switching during reviews. It teaches new developers what "done" means in your team.
If you're serious about DevOps, adding pre-commit to your workflow is one of the easiest and highest-leverage moves you can make.
Want to try it yourself? Open a free Caparra account. You’ll get access to tools that help DevOps teams adopt better practices, reduce toil, and build infrastructure that runs like clockwork.