How to set up a pre commit check.

•

•

Catch It Before It Leaves Your Machine

I remember sitting in a dim home office three years ago, staring at a Slack notification from an on-call engineer while my coffee went cold. We had pushed a tiny, trivial change—nothing more than a misplaced semicolon and a stray debug print—that managed to bypass our CI and nuked the payment gateway during peak hours. That was the night I realized that talking about “quality gates” in high-level architecture meetings is useless if you don’t actually know how to set up a pre commit check that works on a developer’s local machine. You can have the most expensive linting suite in the world, but if it doesn’t catch the stupid mistakes before they leave the workstation, it’s just theater.

I’m not here to sell you on some bloated, enterprise-grade workflow that takes ten minutes to run every time you try to save a file. I’ve spent enough time in the trenches of logistics and insurance systems to know that if a tool is too slow, people will just start using `–no-verify` to bypass it, which defeats the entire purpose. Instead, I’m going to show you a practical, lightweight approach to automating these checks. We’ll focus on setting up a configuration that is fast enough to be invisible but robust enough to keep you from getting that dreaded 2 AM phone call.

Table of Contents

Preventing Bad Commits Before They Reach Production

Preventing Bad Commits Before They Reach Production

The problem with relying solely on CI/CD pipelines is that they are reactive. By the time a build fails in the cloud, you’ve already pushed the code, broken the build for everyone else, and started the inevitable cycle of “fix” commits that clutter the history. I’ve spent too many hours cleaning up those messy, incremental commits that exist only because someone forgot a semicolon or a trailing comma. Moving those checks to the local environment via git hooks automation changes the math. It shifts the friction from the shared pipeline to the individual developer, which is exactly where you want it.

If you are working in a Node environment, I usually reach for husky for javascript developers because it’s relatively painless to get running. It acts as the gatekeeper between your local workspace and the remote repository. The goal isn’t to annoy your teammates with long wait times, but to ensure that automated code quality checks—like linting or basic unit tests—run before the code even leaves your machine. It’s a small trade-off: you lose three seconds during a commit, but you save thirty minutes of debugging a failed build later. It’s about preventing bad commits from ever becoming a problem for the rest of the team.

Automated Code Quality Checks as Your Safety Net

Automated Code Quality Checks as Your Safety Net

I’ve spent too many hours staring at a CI/CD pipeline that failed because of a missing semicolon or a trailing space. It’s a massive waste of compute and, more importantly, a waste of my time. This is where automated code quality checks move from being a “nice-to-have” to a necessity. If I can catch a linting error or a broken test suite on my local machine before the code even leaves my laptop, I’ve saved the entire team from a broken build. It’s about improving developer workflow efficiency by shortening the feedback loop. The sooner you know you messed up, the cheaper it is to fix.

For most of my recent work, I’ve leaned heavily on git hooks automation to handle this heavy lifting. If you’re in the Node ecosystem, using husky for javascript developers is pretty much the industry standard for a reason—it just works. I usually pair it with `lint-staged` so I’m not running a full suite against ten thousand files every time I want to save a small change. The goal isn’t to create a bureaucratic hurdle that slows us down; it’s to build a safety net that catches the stupid mistakes—the ones we all make when we’re tired or rushing to finish a sprint.

Five things I learned from breaking things (so you don't have to)

  • Keep the hook fast. If your pre-commit suite takes more than ten seconds to run, people will start using `–no-verify` just to get their work done, and then the whole safety net becomes a suggestion rather than a rule.
  • Don’t run everything locally. Use the pre-commit hook for things that are cheap and fast, like linting or checking for trailing whitespace, but leave the heavy-duty stuff like full integration tests for the CI pipeline.
  • Make it easy to install. I’ve seen too many setups that require a PhD to get running; if a new dev can’t get the hooks working with a single command, they won’t use them, and the quality will drift.
  • Fail loudly but clearly. If a check fails, the error message shouldn’t be a cryptic stack trace; it should tell the developer exactly what went wrong and, ideally, how to fix it without a Google search.
  • Version control your hook configuration. Treat your `.pre-commit-config.yaml` like actual code—it belongs in the repo so that the entire team is running the exact same set of gates, preventing the “it worked on my machine” excuse.

The bottom line

Treat pre-commit hooks as a way to save your future self from a middle-of-the-night pager alert, not as an extra hurdle in your workflow.

Keep the checks fast; if a hook takes more than a few seconds, people will start using `–no-verify` to bypass them, which defeats the entire purpose.

Automate the boring stuff—linting, formatting, and basic type checks—so you can spend your mental energy on the actual logic instead of arguing about trailing commas.

The Cost of Doing Nothing

Avoiding errors: The Cost of Doing Nothing.

At the end of the day, setting up pre-commit hooks isn’t about being a perfectionist or following some arbitrary style guide just because it’s trendy. It’s about building a mechanical barrier between a momentary lapse in judgment and a broken deployment. We’ve covered how to automate linting, run basic test suites, and catch those annoying syntax errors before they ever leave your local machine. The goal is to move the friction from the CI/CD pipeline—where it costs time and wakes people up—to your local terminal, where it’s just a minor annoyance that takes five seconds to fix. If you take away one thing, let it be this: automate the boring stuff so you can focus on the hard stuff.

I’ve spent enough time in on-call rotations to know that most “catastrophic” failures are actually just preventable mistakes that slipped through the cracks. We like to think of ourselves as infallible engineers, but we aren’t; we’re just people who get tired, distracted, and occasionally forget to run the tests. Implementing these checks is an act of professional empathy for your future self and the person who has to pick up your pager next month. Don’t wait for the next 2 AM incident to realize your workflow is leaking. Set it up now, let the machine do the heavy lifting, and protect your sleep.

Frequently Asked Questions

What happens if a check is too slow and starts killing my productivity during a tight deadline?

I’ve been there. You’re racing to push a hotfix and suddenly you’re staring at a spinning wheel because your linter decided to run a full suite of heavy integration tests. It’s infuriating.

How do I handle cases where a developer absolutely needs to bypass a check to push an emergency fix?

Look, we’ve all been there. It’s 3 AM, the site is down, and your linting rule is blocking a critical hotfix because of a missing trailing comma. In those moments, you use `–no-verify`. It works, but it’s a debt you’re taking out. My rule is simple: if you bypass a check, you must immediately follow up with a “fix-up” commit once the fire is out. Don’t let the emergency become the new baseline.

Should I be running these same checks in the CI/CD pipeline, or is that redundant?

It’s not redundant; it’s your last line of defense. Think of pre-commit hooks as your personal safety net and the CI/CD pipeline as the gatekeeper. I’ve seen plenty of “clever” teammates use `–no-verify` to bypass local hooks when they were in a rush. If you don’t run the same checks in CI, those bypassed commits will eventually hit production and wake you up. Run them in both. One saves you time; the other saves your sanity.

How do we ensure that every single person on the team is actually using the same local configuration?

The “it works on my machine” excuse is a lie we tell ourselves until the CI pipeline fails for the tenth time. To stop the drift, I don’t rely on people manually copying a `.pre-commit-config.yaml` file. I check that file directly into the root of the repo. If it’s in version control, it’s the source of truth. I also add a small script to our setup process that runs `pre-commit install` automatically. It’s not fancy, but it works.

Tomasz Wierzbicki

About Tomasz Wierzbicki

I am not interested in arguing about frameworks. I am interested in why the thing broke at two in the morning and what would have prevented it. Most of what I know came from being wrong in production and writing it down afterwards.