Build fails with Cannot find module './utils/superhelper'. You grep the whole project and the file doesn’t exist. Claude Code, Cursor, or Codex invented an import to a utility it “expected to be there.” Based on training data and similar-project naming conventions, the model completed a plausible-looking path without ever creating the actual file.
Fastest fix: run npx tsc --noEmit (or tsc --build) to get the exact line of every broken import, then for each one either point it at the real equivalent that already exists, or have the agent actually create the file. There are only two valid outcomes per import: implement it, or replace it.
One important exception: if the missing thing is an npm package (not a local file), do not blindly npm install the name the AI wrote. Hallucinated package names are now an active supply-chain attack vector (“slopsquatting”). Verify the package is real and maintained first. Details below.
Common causes
Ordered by hit rate, highest first.
1. Agent confused its plan with its code
In reasoning it said “I’ll use a formatDate utility.” In code it just wrote import { formatDate } from './utils/formatDate' — it treated the name from its plan as already-implemented code.
// agent wrote
import { formatDate } from '@/utils/formatDate';
// but the project actually uses
import dayjs from 'dayjs';
How to spot it: Inspect the reasoning trace. Did it ever say “create” or “implement”? If there’s an import but no creation action, this is it.
2. Defaulted to a utility it “expects” to exist
LLMs have seen src/utils/cn.ts, src/lib/utils.ts, src/helpers/format.ts in so many codebases that they assume yours has them too. TypeScript starters and Next.js / shadcn/ui ship lib/utils.ts (with the cn() helper) by default, so the model defaults to it even when you never ran npx shadcn init.
How to spot it: Search for stock template paths like src/lib/utils, src/utils/cn, @/lib/db. If you never created them, that’s the bug.
3. Package name from training data (and “slopsquatting”)
Wrote import { useDebounce } from 'react-use' but you installed usehooks-ts, not react-use. Error: Cannot find module 'react-use'. Technically package hallucination, not file hallucination, but the symptom is identical.
This one carries real risk. A USENIX Security 2025 study of 576,000 code samples across 16 models found 19.7% of generated package dependencies were hallucinated (about 5.2% for commercial models, 21.7% for open-source ones), with 58% of fake names repeating across runs. Attackers register those predictable fake names on npm/PyPI so the next person who installs the AI’s suggestion pulls malware. Documented real cases include react-codeshift (a conflation of the real jscodeshift and react-codemod) and unused-imports (squatting on the real eslint-plugin-unused-imports). So treat an unknown package name as suspect, not as a typo to wave through.
How to spot it: Check package.json; npm ls <package> (or npm list) returns nothing — the package was never installed. See “Verify a package before installing it” below before you add it.
4. Agent took the wrong workspace path in a monorepo
packages/ui/src/Button.tsx exists, but the agent (working in packages/app) wrote import { Button } from '../ui/Button' — the relative math is wrong. The file exists but the path doesn’t resolve.
How to spot it: The path in the error is relative and looks suspicious (../../../ui/); check the tsconfig paths/references and the actual directory depth.
5. Casing mismatch (works on macOS, fails in CI)
import './Button' but the file is button.tsx. macOS and Windows (case-insensitive filesystems) resolve it; a Linux CI build fails. Not strictly hallucination, but the symptom is identical and it’s a top reason for “builds locally, breaks in CI.”
How to spot it: git ls-files | grep -i button shows the actual on-disk casing. Setting "forceConsistentCasingInFileNames": true in tsconfig (the default in TS 5.x) surfaces this locally instead of only in CI.
6. Agent imported a file it planned to create but forgot
In the plan: “I’ll create src/api/posts.ts.” In the code: only the import, never the creation — the context window cut off mid-plan or a step got skipped.
How to spot it: Grep the agent’s Write/Edit tool calls for this run; if the missing file isn’t in the list, it was never written.
Shortest path to fix
Ordered by ROI. Most cases fix in under 5 minutes.
Step 1: List every hallucinated import with tsc --noEmit
npx tsc --noEmit 2>&1 | grep -E "TS2307|TS2305|Cannot find"
You’ll see something like:
src/components/Form.tsx:5:23 - error TS2307: Cannot find module '@/utils/superhelper'
src/pages/posts.tsx:8:30 - error TS2307: Cannot find module '@/lib/db'
src/hooks/useAuth.ts:3:15 - error TS2305: Module '@/utils' has no exported member 'verifyToken'
TS2307 means the module/file isn’t found at all. TS2305 means the file is found but it doesn’t export the named thing (the agent invented an export, or got the name wrong). One row, one fix. If you don’t use TypeScript, ESLint’s import/no-unresolved rule or just running the build (vite build, next build) gives you the same list.
Step 2: Decide whether the missing thing should exist
Open the failing file, look at how the import is used:
import { superhelper } from '@/utils/superhelper';
// ...
const result = superhelper(data, { format: 'json' });
Ask two questions:
- Does the project already have an equivalent? (e.g.
lodash,dayjs, a helper you wrote earlier) - If not, is it worth a new file?
| Situation | Fix |
|---|---|
| Equivalent already exists in the repo | Change the import, don’t create a file |
Tiny feature (< 20 lines) | Inline it at the call site |
| Genuinely reusable utility | Actually create the file |
| Local path typo / monorepo path math | Fix the relative path or use a tsconfig alias |
| Casing mismatch (TS2307 only on CI) | Rename the import to match on-disk casing exactly |
| Missing npm package | Verify it’s real (next section), then install the real one or swap to an installed equivalent |
Step 3: Grep to find an existing equivalent
Verify before creating:
# By function name
grep -rn "formatDate\|format_date\|dateFormat" src/
# By export signature
grep -rn "export function.*Date.*string" src/
# Look for related installed packages
grep -E "(date|format|debounce|throttle)" package.json
If something’s there, swap the hallucinated import for it.
Step 4: Verify a package before installing it
If the missing import is a package, do this before npm install. A hallucinated name might be unregistered (harmless build error) or, worse, already squatted by an attacker.
# Does it exist on the registry, and how popular is it?
npm view react-use # 404 = doesn't exist; or shows version/maintainers
npm view react-use time # check first-publish date — brand-new + your-exact-name is a red flag
Sanity-check before adding it:
- Weekly downloads / repo: open the package on npmjs.com or pypi.org. A real utility has a GitHub repo, history, and non-trivial downloads. A few dozen weekly downloads on a “popular-sounding” name is suspicious.
- Name conflation: is the AI’s name a mashup of two real packages (like
react-codeshiftfromjscodeshift+react-codemod)? Search for the two halves separately — the real package is usually one of them. - Publisher: trust the publisher and history, not the name. Slopsquatting relies on you trusting a plausible name.
When in doubt, search for what is the package for X in <ecosystem> and install the canonical one yourself, rather than the string the AI emitted.
Step 5: Hand the list back to the agent with a precise prompt
Don’t fix every import by hand — feed the list back:
Build is broken. These imports reference non-existent files or packages:
[paste tsc --noEmit output]
Fix rules (strict):
1. Do not create new files unless there is truly no equivalent already in the repo
2. Before fixing each import, grep the project to check for an existing implementation
3. For any missing npm package, STOP and tell me the name + npm weekly downloads.
I decide whether to install or swap. Do not run npm install yourself.
4. After fixing, `tsc --noEmit` must be fully green
5. Do not change tsconfig paths
Step 6: For genuinely new files, require a proposal first
If a utility really needs to be created:
You want to create src/utils/X.ts. First output:
1. Full file content (with types)
2. Every call site that will use it
3. Why not use existing lodash / dayjs / native APIs
Wait for my approval before creating it.
This forces a human review and stops the agent from sprinkling one-call-site “helpers” everywhere.
How to confirm it’s fixed
Run, in order:
npx tsc --noEmit # must exit 0, no TS2307 / TS2305
npm ls 2>&1 | grep -i "missing\|invalid" # no missing/extraneous deps
npm run build # the actual build must pass
A green local tsc plus a clean build is the real “done.” If you only fixed editor squiggles but the build still fails, you missed a path that TypeScript doesn’t check: a runtime-only require, an asset import, or a case-sensitivity issue that only bites on Linux CI. Re-run the build on a case-sensitive checkout or in CI to catch cause #5.
FAQ
Why does the AI keep inventing imports instead of admitting it doesn’t know?
LLMs generate the next most-likely token, and after import { formatDate } from a path is the most likely continuation. The model has no built-in “does this file exist?” check unless it’s an agent with file-search tools and actually uses them. That’s why your fix loop should force a grep/tsc check, not trust.
Is it dangerous to just npm install the package name it suggested?
Yes, potentially. About 1 in 5 AI-suggested package names don’t exist (USENIX Security 2025), and attackers pre-register the common fakes (so-called “slopsquatting”). Always confirm the package is real, maintained, and the one you actually want before installing. Run npm view <name> and check the repo and download count first.
tsc --noEmit is clean but the build still fails — why?
TypeScript only checks what it type-checks. Dynamic require(), image/CSS/asset imports, and case-only differences on Linux slip past it. Run the real build (next build, vite build, etc.) and, ideally, on a case-sensitive filesystem or in CI.
How do I tell file hallucination from a casing/path bug?
If git ls-files | grep -i <name> finds the file, it’s a casing or relative-path bug (causes 4 and 5), not a hallucination — the file exists, the import string is just wrong. If nothing turns up anywhere, it was never created.
How do I stop this from happening every session?
Put a hard rule in CLAUDE.md / AGENTS.md / .cursorrules: “Before importing, grep/find to confirm the target exists; never invent a path; for any new package, stop and ask.” Add tsc --noEmit to the agent’s definition of done, and add a pre-commit hook so a TS2307/TS2305 can’t be committed.
Prevention
- In
CLAUDE.md/AGENTS.md/.cursorruleswrite: “Before importing, you mustgreporfindto confirm the target exists. Never invent a path. Never install a package without confirming it’s real.” - Add
tsc --noEmitto the agent’s “done” criterion — declaring done requires a clean type check. - Pre-commit hook runs
tsc --noEmit;TS2307/TS2305blocks the commit. - Drop a
docs/utils.mdat the project root listing common helpers and their real paths — feed it to the agent as context. - Verify the agent has file-search tools available and uses them — many agents don’t search the file tree proactively by default.
- Keep
npm installin its own commit so you can review exactly what was added (and whether the name is real) any week. - In monorepos, use tsconfig
pathsaliases instead of deep relative paths — the hallucination/path-math rate drops noticeably. - Set
"forceConsistentCasingInFileNames": truein tsconfig so case mismatches fail locally, not just in CI.
Related
- AI code broke build
- Build passes locally but fails in CI
- Cursor missed project context
- AI pre-commit review workflow
- Claude Code SEO audit
- AI dependency upgrade workflow
Tags: #AI coding #Debug #Troubleshooting