A hallucinated import that a type checker would catch in CI still costs a full round trip through a pipeline before anyone finds out. Catching it locally, before the commit even lands, is cheaper and faster for everyone involved. Here is how to wire that check into a pre-commit hook so it runs automatically, without relying on anyone remembering to run it by hand.
Why Local Beats CI for This Specific Check
CI catches a hallucinated import eventually, but "eventually" here means after a push, after a pipeline queues, after a build finishes, and after someone checks the result. A local pre-commit hook catches the same problem in seconds, before the commit is even created, while the context is still fresh in the developer's head. For a check this cheap to run, there is no good reason to defer it to a slower, more expensive stage of the pipeline.
Setting Up the Hook Framework
The pre-commit framework is the standard tool for wiring checks like this into git, regardless of language. It manages hook installation, versioning, and execution across a team without every developer needing to configure git hooks manually. A minimal .pre-commit-config.yaml for a Python project might reference a handful of hooks that run on every commit, and adding one more for import verification fits the same pattern.
Verifying Imports Actually Resolve
For Python, the simplest local check that catches both a genuinely missing package and a hallucinated one is attempting to actually import every module referenced in changed files, inside the project's real virtual environment, and failing the commit if any import raises ModuleNotFoundError. This alone catches package hallucinations immediately, since a package that does not exist in the environment cannot be imported, full stop.
For catching hallucinated method calls rather than missing packages entirely, a static type checker does more of the work. Running mypy as a pre-commit hook flags a call to a method or attribute that a typed library genuinely does not expose, which is exactly the signature of a hallucinated API surface: real package, invented method.
Doing the Same for JavaScript and TypeScript
For JavaScript and TypeScript projects, a strict ESLint configuration with import resolution rules enabled catches an unresolved import the same way, at commit time rather than at build time. Combined with TypeScript's own compiler checks for member access on typed objects, the pairing catches both categories: an import that resolves to nothing, and a method call on a real object that was never actually defined.
Keeping the Hook Fast Enough That Nobody Disables It
The single biggest risk to any pre-commit hook is that it becomes slow enough that developers start reaching for --no-verify to skip it. Scope the import and type check to only the files actually changed in the commit, not the entire codebase, and cache whatever the tooling allows caching. A hook that adds two or three seconds to a commit gets used. A hook that adds thirty seconds gets bypassed within a week, which defeats the entire point of putting the check this early in the workflow.
A Minimal Working Example
A stripped-down .pre-commit-config.yaml for a Python project doing this might chain together an import-resolution check and a type-checking pass as separate hooks, both scoped to changed files only:
repos:
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.10.0
hooks:
- id: mypy
args: [--ignore-missing-imports]
- repo: local
hooks:
- id: verify-imports
name: verify imports resolve
entry: python -c "import importlib,sys; [importlib.import_module(m) for m in sys.argv[1:]]"
language: system
files: \.py$
This is intentionally minimal. Real configurations usually layer in formatting and linting hooks alongside it, but the two hooks above are the ones doing the actual hallucination-catching work: one confirms every import resolves to something installed, the other confirms every method call matches the library's real type definitions.
Handling the JavaScript Equivalent Locally
The same pre-commit framework supports non-Python hooks through its language-agnostic hook definitions, so a JavaScript or TypeScript project can wire ESLint's import-resolution rules and the TypeScript compiler's --noEmit check into the same local pre-commit flow, catching an unresolved import or an invalid member access before the commit exists rather than after a build fails in CI.
Backstopping With CI Anyway
A local hook is not a substitute for the same check running in CI, it is a faster first pass. Developers can still skip local hooks intentionally or accidentally, and CI is the backstop that guarantees nothing merges without the check actually running somewhere it cannot be bypassed. Configuring the same import and type verification as a required CI step, documented at GitHub's own documentation for required status checks, closes the gap for anyone who skipped the local version.
What to Do When the Hook Actually Catches Something
The first time this hook fires on a real hallucinated import, it's worth treating as useful signal rather than an annoyance to dismiss quickly. Check whether the same prompt or a similar one produces the same hallucinated name again, since that repeatability is exactly the pattern that makes a name worth watching for elsewhere in the codebase too. If a teammate hits the same false suggestion independently, it's worth a quick note in the team's shared documentation or prompt library, since the next person asking the assistant something similar will likely see the same invented name.
Rolling This Out Without a Big Migration
None of this needs to land as one large change. Start with the import-resolution check alone, since it's the simplest to configure and catches the loudest failure mode, package hallucination, with almost no false positives. Add the type-checking hook once the first one is stable and the team is comfortable with the pre-commit workflow generally. Trying to introduce both simultaneously, on a codebase that has never had either, tends to surface a wave of pre-existing type errors unrelated to AI hallucination that can make the whole effort feel more disruptive than it needs to be.
What This Buys You
None of these checks are exotic, they are the same static analysis tooling most mature codebases already run for other reasons. The specific value here is treating "does this import or method actually exist" as one more thing that tooling checks automatically, rather than something a human has to remember to verify every time an AI coding assistant suggests a new dependency or an unfamiliar method call. Once it is wired in, it runs the same way every time, and it never gets tired or skips a step because it is the fifth suggestion reviewed that afternoon.
A Note on Performance at Scale
For larger codebases, running a full type-check on every commit, even scoped to changed files, can still add up if a change touches a file with a large dependency graph. Most type checkers support an incremental or cached mode specifically for this, mypy included, and enabling it is worth doing from the start rather than after the hook has already become slow enough that people start grumbling. A hook that stays fast is a hook that stays on.
If your team is setting up an AI coding assistant workflow and wants this kind of guardrail built in from the start rather than bolted on after an incident, this AI automation team at 137Foundry has done exactly this kind of setup for clients integrating assistants into real production codebases.
Top comments (0)