How to Set Up Pre-Commit Hooks That Catch AI Coding Mistakes Before They Ship

A terminal window showing monospace code on a dark screen

An AI coding assistant can write a working function in seconds, and that speed is exactly why mistakes slip through. A human writing the same function slowly notices the missing null check or the package that does not exist. A reviewer skimming a fast-moving pull request often does not. By the time the change reaches review, the author has moved on to the next prompt, and the reviewer is left verifying something they did not write.

Pre-commit hooks solve a narrow but real part of this problem. They run automatically before a commit is recorded, they catch a specific class of mistakes consistently, and they do it before the code ever reaches a human reviewer's queue. They are not a replacement for review. They are a cheap, fast filter that removes the mistakes a person should not have to spend attention on.

terminal screen close monospace code
Photo by K on Pexels

Why AI-Generated Code Needs a Different Kind of Gate

Traditional lint rules were built around the mistakes humans make: inconsistent formatting, unused variables, missing semicolons. AI-generated code makes those mistakes too, but it also makes a different set that most linting configs were never tuned for.

The assistant will occasionally reference a package that sounds plausible but does not exist, a pattern sometimes called a hallucinated dependency. It will write code that imports a real library but calls a method that library does not have, because the training data blended two similar APIs. It will duplicate logic that already exists elsewhere in the codebase because it did not have visibility into that file when it generated the suggestion. None of these are formatting problems, and none of them show up in a standard ESLint or Prettier pass.

A pre-commit hook chain built for this moment needs to check for things beyond style. It needs to verify that every import actually resolves, that no new secret or credential made it into the diff, and that changes to the most sensitive parts of the codebase get flagged for a closer look regardless of how confident the commit message sounds.

What a Pre-Commit Hook Can and Cannot Catch

It helps to be precise about scope before building anything. A pre-commit hook runs locally, on the developer's machine, against the files staged for commit. It is fast by necessity, usually a few seconds, because developers will route around anything slower.

That speed constraint means hooks are good at syntactic and structural checks: does this import resolve, does this file contain something that looks like an API key, does this diff touch a path on a protected list. Hooks are bad at judgment calls: is this the right architectural approach, does this introduce a subtle race condition. Those questions still belong to a human reviewer, and ideally to automated tests running in CI after the commit lands. The goal is not to replace review. It is to keep mistakes that do not need a human's judgment out of a human's queue.

Setting Up the Hook Runner

Most teams manage hooks through a dedicated runner rather than raw git hook scripts, because raw scripts are not versioned with the repository and do not get distributed automatically when someone clones it. The pre-commit framework is the most common choice for mixed-language repositories and works by defining a .pre-commit-config.yaml file that lists each check and the files it should run against.

JavaScript and TypeScript teams often reach for Husky instead, which wires hooks through the npm lifecycle so they install automatically with npm install. Either tool does the same job: it intercepts the commit, runs a defined list of checks against the staged files, and blocks the commit if any check fails, wrapping the native git hooks system to make configuration shareable and versioned.

Whichever runner a team picks, the configuration file should live in the repository, not in a developer's local git config. A hook that only exists on one machine protects nothing once a second developer, or an AI assistant acting on someone's behalf, pushes a commit from a different environment.

notebook open pages desk pen
Photo by MESSALA CIULLA on Pexels

Catching Invented Dependencies and Dead Imports

The hallucinated dependency problem deserves its own check, because standard linting will not catch it. A package that does not exist on the registry will fail at install time in the best case, or get silently typosquatted by someone who registered that exact invented name in the worst case.

A pre-commit step that diffs new import statements against the lock file and the registry catches this before it becomes a runtime failure or a supply chain risk. The check is mechanical: for every new import, confirm the package already resolves. If it does not, the commit is blocked with a message naming the specific import.

The same logic applies to internal imports. Assistants working without full repository context sometimes reference a function that was renamed or removed in a previous refactor. A static check that every internal import resolves to an existing file and export catches this before it becomes a build failure someone else has to debug.

Linting and Formatting Rules Tuned for AI Output

Standard linting still matters, and it should run in the same hook chain rather than as a step developers might skip. ESLint for JavaScript and TypeScript, or the equivalent linter for the project's language, catches the baseline issues: unused variables, unreachable code, inconsistent typing.

What is worth adding on top of the default rule set is a small number of rules specifically aimed at patterns AI assistants tend to produce. A rule that flags duplicate function definitions across files catches the case where an assistant reimplements a helper that already exists elsewhere in the codebase because it generated the suggestion without full context. A rule that flags overly broad exception handling, like a bare catch block that swallows every error type, catches a pattern assistants produce often because it makes code look like it handles errors without actually handling anything specific.

These additions are small, but they target the exact failure modes that generic lint configs miss, and they cost nothing extra to run since they ride along in the same pass as the standard rules.

Secret and Credential Scanning

This check matters regardless of who wrote the code, but it matters more with AI-assisted workflows because assistants sometimes copy configuration patterns from training data that include example credentials, and a developer moving fast can paste a real key into a config file meant to hold a placeholder.

A secret-scanning hook checks every staged file for patterns that look like API keys, access tokens, or private key headers, and blocks the commit if it finds one. The OWASP Cheat Sheet Series documents common credential formats worth matching against, and most scanning tools ship with a maintained pattern list covering the major cloud providers already.

The check should run against the full diff, not just new files, since a credential can just as easily land in a file that already exists. It is one of the cheapest checks to run and one of the costliest mistakes to let through, so it belongs early in the chain.

Blocking Changes to Sensitive Paths

Some parts of a codebase deserve a higher bar than "the tests pass." Authentication logic, billing calculations, and permission checks are where a subtle mistake does not throw an error. It just quietly lets the wrong person through, or charges the wrong amount, and nobody notices until a support ticket arrives weeks later.

A path-based hook rule flags any commit touching files under a defined list of sensitive directories, regardless of what the diff contains. The commit is not blocked outright, but it is tagged, and the PR description must include an explicit note confirming the change was reviewed with that sensitivity in mind. This removes the possibility of an AI-assisted commit to the billing logic sliding through on a confident-sounding commit message alone.

Keeping Hooks Fast Enough That Developers Don't Disable Them

A hook chain that takes thirty seconds will get bypassed with --no-verify within a week, and once developers learn that flag exists, it gets used for legitimate commits too. Speed is not a nice-to-have here. It is the difference between a check that runs and one that only exists in the configuration file.

Scope every check to the staged files, not the whole repository, which is the single biggest lever for keeping the chain under a few seconds. Run slower checks, like a full type-check or a broader static analysis pass through a tool like Semgrep, in CI after the commit lands instead. The local hook should catch what is cheap to catch; everything else belongs downstream.

"The teams that actually keep their hooks running long-term are the ones who treat speed as a hard requirement, not a nice-to-have. The first time a hook slows someone down during a demo, they will find the bypass flag and never look back." - Dennis Traina, founder of 137Foundry

Rolling Hooks Out Across a Team Without Friction

Introducing a new hook chain to an existing team goes better as a staged rollout than as an overnight mandate. Start with checks that have zero false positives, like secret scanning and import resolution, and let those run as hard blocks from day one since nobody argues with a check that is never wrong.

Add judgment-adjacent checks, like sensitive-path tagging, as warnings first. Let the team see what gets flagged for a week or two before making it a hard requirement, which surfaces any path mislabeled as sensitive before it starts blocking real work.

Document the override process clearly and make it visible rather than hidden. A developer who hits a genuine false positive should be able to note why and proceed, with that override logged for a lead to review later. A hook chain with no escape hatch gets bypassed the first time it is wrong; one with a visible, logged escape hatch gets trusted.

whiteboard sketches diagram planning team
Photo by Alena Darmel on Pexels

What Still Requires Human Review, and Where CI Backs Up the Hook

None of this replaces the judgment a human reviewer brings to a pull request. Hooks catch mechanical failures: broken imports, leaked secrets, touched sensitive paths. They do not catch whether the approach is sound or whether a reasonable-looking piece of logic quietly contradicts a rule defined elsewhere. Getting mechanical checks off the reviewer's plate frees that attention for the questions a hook cannot answer.

Local hooks alone are not sufficient, since they only run on a machine that has them installed correctly. A second layer of the same checks, run in GitHub Actions or an equivalent CI pipeline on every pushed commit, catches the case where a hook was skipped, misconfigured, or bypassed with an override flag. Running the same check twice, once locally for speed and once in CI for certainty, is the difference between a check that is merely recommended and one that is actually enforced.

Closing Note

The specific mistakes AI coding assistants introduce are narrow enough to catch with the right set of pre-commit checks: invented dependencies, duplicated logic from missing context, overly broad error handling, and the occasional credential copied from a training example. None of these require a human to catch in review if the hook chain is built to catch them first.

The services we offer at 137Foundry include setting up exactly this kind of tooling for teams adopting AI-assisted development, and the services hub covers the broader engineering work around it. Teams that get this right spend their review time on the judgment calls that actually need it, and that shift alone tends to be worth more than any single rule in the hook chain. Read more about how we approach this work on our homepage or the about page.

Need help with your next project?

137Foundry builds custom software, AI integrations, and automation systems for businesses that need real solutions.

Book a Free Consultation View Services