Skip to main content
· CI/CD · 3 min read

Standardizing Code Quality Across Teams with Pre-Commit CI

Every CI pipeline that’s failing because of a formatting issue is waste. A linter check that could have run in 3 seconds locally is instead burning cloud minutes on a runner. Pre-commit hooks fix this. However, they only work if they’re configured correctly and run in CI as a safety net.

The Pre-Commit Pipeline

%%{ init: { 'look': 'handDrawn' } }%%
graph TD
    A[git commit] --> B[Pre-commit hooks run locally]
    B --> C{All hooks pass?}
    C -->|No| D[Commit rejected<br/>dev fixes locally]
    C -->|Yes| E[Commit lands on branch]
    E --> F[CI pipeline triggered]
    F --> G[Pre-commit CI also runs<br/>as backup safety net]
    G --> H[PR ready for review]

The local hook catches trivial issues immediately. Pre-commit CI (the cloud service) runs on every PR as a backup — it catches issues devs forgot to set up locally.

Setting Up 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: 24.1.1
    hooks:
      - id: black

  - repo: https://github.com/pyupio/safety
    rev: 3.0.1
    hooks:
      - id: safety
        args: ["--file=requirements.txt"]

Install with:

pip install pre-commit
pre-commit install

Now every git commit runs the hooks locally first.

Terraform Formatting in Pre-Commit

For infrastructure teams:

  - repo: https://github.com/antonbabenko/pre-commit-terraform
    rev: v1.83.0
    hooks:
      - id: terraform_fmt
      - id: terraform_validate
      - id: terragrunt_fmt
      - id: tfsec

This catches terraform fmt issues and validates HCL syntax before any terraform plan runs in CI.

Adding a GitHub Actions Safety Net

Even with local hooks, a developer might skip installation or temporarily disable a hook. Pre-commit CI runs on every PR opened:

# .github/workflows/pre-commit.yml
name: pre-commit

on:
  pull_request:
  push:
    branches: [main]

jobs:
  pre-commit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v4
      - uses: pre-commit/[email protected]

This catches anything that slips through local hooks — formatting, secret scanning, YAML validation.

What Pre-Commit Catches That CI Shouldn’t Have To

CheckWhy It Belongs in HookCI Alternative
Formatting (black, gofmt, prettier)Runs in millisecondsWould waste runner minutes
Secret scanning (git-secrets, trufflehog)Catches before pushToo late if already in repo history
Large file detectionPrevents bloat in Git historyHard to clean up later
YAML/TOML validationInstant feedbackCI would fail on obvious syntax errors
Terraform fmt/validateCatches HCL errors before planterraform plan would fail anyway

The Shared Quality Bar

The real value of pre-commit hooks isn’t catching formatting — it’s establishing a team-wide contract. When everyone has the same linter running locally, PRs stop being blocked on style nits. Reviewers focus on logic, not whitespace.

Pre-commit CI extends this: new team members get the same quality bar automatically, without needing to manually set up their local environment.

The CI half of that deserves the same care as the hooks themselves — pinning tool versions explicitly rather than trusting whatever the runner image ships this month, which I went into in GitHub Actions and template actions.

Run locally. Run in CI. Ship clean.