The pre-commit Playbook

pre-commit is a Key Tool in the Caparra DevOps 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:

  1. Use virtual environments per project. With Python, use python -m venv .venv, or a tool like poetry or uv. Install pre-commit locally with your dev dependencies.

  2. Activate the environment before development. Automate this with direnv, Makefile, or a dev.sh script.

  3. Use pre-commit install to hook into Git. This adds a script to .git/hooks/pre-commit that runs your configured checks.

  4. 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 noise

  • end-of-file-fixer: adds a newline to avoid subtle issues in POSIX systems

  • check-merge-conflict: makes sure you didn’t leave behind <<<<<<< HEAD

  • detect-aws-credentials: stops you from leaking secrets

Language-specific faves:

  • Python: black, ruffisort

  • 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 a CONTRIBUTING.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: and exclude: to tune scope.

  • Conflicts with IDEs? Align formatter config (like black and prettier) 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.

Next
Next

A Surprising Secret About Python Linters