DEV Community

Cover image for The Most Underrated Tool in Your Dev Toolbox: Pre-Commit Hooks (Yes, That 20-Year-Old Git Feature)
Mr. 0x1
Mr. 0x1

Posted on

The Most Underrated Tool in Your Dev Toolbox: Pre-Commit Hooks (Yes, That 20-Year-Old Git Feature)

Look, I've been slinging code for longer than I care to admit, and one thing never fails to blow my mind: teams dropping serious cash on massive CI pipelines just to catch the dumbest mistakes imaginable.

We're out here pushing broken code on purpose, waiting for GitHub Actions or some cloud runner to slap our wrists and say "bad developer." It's like paying a bouncer to tell you your shoes are untied after you've already tripped down the stairs.

There's a better way. A way that's instant, local, free, and doesn't make you wait eight minutes to learn that ESLint is mad at you again.

It's called pre-commit hooks. And yeah, they're ancient. But they're also brutally effective.

What the Heck Is a Pre-Commit Hook, Anyway?

At its core, a pre-commit hook is just a script that Git runs right before it seals the deal on your commit.

  • Script exits with code 0 → Commit goes through.
  • Script exits with anything else → Commit aborted. Fix your mess.

No YAML hell. No spinning runners. No "let's push to main and see what breaks."

Just pure, local enforcement.

The Real Cost of "We'll Catch It in CI"

We've normalized this weird ritual where basic quality checks happen after the code's already in the repo. Here's what that actually costs you:

Problem Caught Where? Real Cost
Formatting issues CI Minutes per PR, endless nitpicks
Linting failures CI Cloud minutes, context switches
Type errors CI Interrupted flow
Leaked secrets CI Potential breaches (too late!)
OpenAPI drift CI Broken contracts downstream
Stale generated code CI Rework and blame games

Every single one of these can—and should—be caught in milliseconds on your machine.

CI is for verification.

Pre-commit is for prevention.

Shifting Left... Like, Way Left

Pre-commit hooks are the earliest possible gate in your entire development lifecycle. They run before:

  • The PR exists
  • CI spins up
  • Reviews happen
  • Anything deploys
  • Incidents occur

You're enforcing standards at the exact moment your intent turns into code. That's power.

A Dead-Simple But Devastating Setup

Here's a minimal pre-commit hook that'll instantly level up your project. Drop this into .git/hooks/pre-commit:

#!/usr/bin/env bash
set -e

echo "🤠 Hold up, partner—running the gauntlet..."

npm run lint || { echo "🚨 Lint's not happy. Fix it."; exit 1; }
npm run format:check || { echo "🧹 Formatting's a disaster."; exit 1; }
npm run typecheck || { echo "🔥 Types are throwing hands."; exit 1; }
npm test -- --runTestsByPath $(git diff --cached --name-only) || { echo "🧪 Tests failed on changed files."; exit 1; }

echo "✅ All clear. Commit away, legend."
Enter fullscreen mode Exit fullscreen mode

Make it executable:

chmod +x .git/hooks/pre-commit
Enter fullscreen mode Exit fullscreen mode

Boom. You've now enforced:

  • Linting
  • Formatting
  • Type safety
  • Targeted tests on changed files

All for the low price of absolutely nothing.

Stop Committing Secrets Before They Even Exist

This one pays for itself the first time it saves your bacon.

Add this to your hook:

if git diff --cached | grep -E "(AWS_SECRET|API_KEY|PRIVATE_KEY|PASSWORD)"; then
  echo "🚨 Whoa there—looks like a secret's trying to escape. Commit aborted."
  exit 1
fi
Enter fullscreen mode Exit fullscreen mode

CI detecting secrets post-commit? That's already game over. The commit exists. The damage is done.

The Sleeper Hit: Hooks as Architecture Enforcement

This is where pre-commit hooks go from "nice to have" to "how did we live without this?"

Use them to enforce contracts across your codebase:

  • Touch openapi.yaml? Auto-regenerate clients and fail if they're stale.
  • Add a migration without a down file? Nope.
  • Bump version without changelog entry? Try again.

Example for OpenAPI:

if git diff --cached --name-only | grep openapi.yaml; then
  echo "🔄 Regenerating clients..."
  make generate-clients

  if ! git diff --quiet; then
    echo "❌ Generated clients are out of date. Stage them!"
    exit 1
  fi
fi
Enter fullscreen mode Exit fullscreen mode

No more PR comments. No CI failures. No "wait, why is the client broken?"

Just correct-by-construction development.

Pre-Commit vs CI: It's Not Even Close

Factor Pre-Commit Hooks CI Pipelines
Feedback speed Instant Minutes
Cost Free $$$ (cloud minutes)
Developer focus Preserved Interrupted
Enforcement Hard stop Advisory (often ignored)
Works offline Yes No

CI says: "Hey, you messed up."

Pre-commit says: "No. Fix it right now."

"But Devs Can Bypass It With --no-verify!"

Sure. And the same person would've skipped CI checks too.

Hooks aren't about stopping determined chaos agents. They're about making the right path the default path for everyone else.

You can't accidentally bypass a hook. That's the point.

It's a Culture Thing

Rolling out pre-commit hooks sends a message:

  • We respect your time
  • We don't outsource basic discipline to cloud services
  • We prevent problems instead of reacting to them
  • We value correctness over vanity velocity metrics

That's mature engineering.

The Iron Law of Pre-Commit Hooks

If it's cheap to check and expensive to fix later, it belongs in a pre-commit hook.

Full stop.

Final Thoughts

We're all sleeping on pre-commit hooks because they're old, boring, and don't have a SaaS dashboard with pretty graphs.

But they work. They've always worked.

And they quietly save teams thousands in CI minutes, review cycles, incident response, and developer burnout.

Next time you're about to add another GitHub Action for something basic, stop.

Write a hook instead.

Your future self will thank you. Your teammates will thank you. Your cloud bill will thank you.

Now go forth and hook responsibly 🤠

Note: For larger teams, consider using the pre-commit framework—it manages hooks across repos and makes sharing configs easy. But even plain Git hooks are a massive win.

Top comments (0)