Build 报 Cannot find module './utils/superhelper',你在项目里 grep 一圈完全没这个文件——Claude Code 或 Cursor 凭空写了一条 import,引用了它”觉得应该存在”的 utility。这类幻觉是 LLM 最常见的失败模式之一:它根据训练数据 + 类似项目的常见命名,“补全”了一个看起来很合理的路径,但实际并没创建对应文件。好消息是这种问题易修——错误信息会精确告诉你是哪一行,修法只有两种(实现它或换掉它)。这篇拆原因、给修复模板、写预防规则。
常见原因
按命中率从高到低排序。
1. Agent 自己混淆了 plan 和 code
它在思考阶段说”我会用一个 formatDate 工具”,结果在 code 阶段直接 import { formatDate } from './utils/formatDate'——它把自己计划里的名字当成了已存在的代码。
// agent 写的
import { formatDate } from '@/utils/formatDate';
// 但项目里实际是
import dayjs from 'dayjs';
如何判断:看 agent 的 reasoning trace,它有没有提过 “create” / “implement”。如果没有”创建”动作只有 import,就是混淆了。
2. 它默认这种 utility 在项目里”应该”存在
LLM 见过太多项目里有 src/utils/cn.ts、src/lib/utils.ts、src/helpers/format.ts,于是默认你的项目也有。typescript template、Next.js / shadcn 默认有 lib/utils.ts,但你的项目可能没用 shadcn。
如何判断:搜常见模板路径——src/lib/utils、src/utils/cn、@/lib/db——看是不是你压根没建过的”标配”路径。
3. 训练数据里见过的开源库 / 包名
它写 import { useDebounce } from 'react-use',但你装的是 usehooks-ts 没装 react-use。错误是 Cannot find module 'react-use'。这其实是包幻觉,不是文件幻觉,但根因相同。
如何判断:检查 package.json 是否真有这个依赖;npm list <package> 没结果就是包幻觉。
4. Agent 在 monorepo 里走错 workspace
你的 monorepo 里 packages/ui/src/Button.tsx 存在,但 agent 在 packages/app 里写 import { Button } from '../ui/Button',路径算错。文件存在但路径不通。
如何判断:错误信息里的路径是相对路径而且层级看着可疑(../../../ui/),就检查 tsconfig 的 paths 设置和实际目录层级。
5. 大小写错误(macOS 本地 OK,CI 挂)
import './Button' 但文件叫 button.tsx,macOS 本地能解析,Linux build 挂。严格来说不是幻觉,但症状类似。
如何判断:git ls-files | grep -i button 看实际文件名大小写。
6. Agent 引用了它”准备创建”但忘了创建的文件
它在 plan 里说”我会创建 src/api/posts.ts”,但实际只 import 没创建——可能是上下文限制提前结束,或者它跳过了那一步。
如何判断:grep 所有 agent 这轮的 Write 工具调用,看 path 列表里有没有缺失的那个文件。
最短修复路径
按收益排序。绝大多数能在 5 分钟内修完。
Step 1:用 tsc --noEmit 一次列出所有幻觉 import
npx tsc --noEmit 2>&1 | grep -E "TS2307|TS2305|Cannot find"
你会得到类似:
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'
每一条对应一个待修。
Step 2:判断这个 utility 该不该存在
打开报错的那个文件,看它怎么用这个 import:
import { superhelper } from '@/utils/superhelper';
// ...
const result = superhelper(data, { format: 'json' });
问自己两个问题:
- 这个功能项目里已经有等价物吗?(如
lodash、dayjs、你自己写过的 helper) - 如果没有,值得为它建一个新文件吗?
| 情况 | 修法 |
|---|---|
| 项目里已有等价物 | 换 import,不建新文件 |
| 是个小功能(< 20 行) | 直接 inline 写在调用处 |
| 是个能被多处复用的工具 | 真的建出来 |
| 是个包幻觉 | npm install 装真包,或换成已装的等价包 |
Step 3:用 grep 找替代
确认是不是已有等价物:
# 按功能名搜
grep -rn "formatDate\|format_date\|dateFormat" src/
# 按签名搜
grep -rn "export function.*Date.*string" src/
# 找已装的相关包
grep -E "(date|format|debounce|throttle)" package.json
如果有现成的,把幻觉 import 替换掉即可。
Step 4:让 agent 自己修,给精确 prompt
不要手动修每一个——把列表喂回 agent:
build 失败。以下 import 引用了不存在的文件:
[贴 tsc --noEmit 的输出]
修复规则(严格遵守):
1. 不要创建新文件,除非该 utility 真的不存在等价物
2. 修每个 import 前先 grep 项目,确认是否有现成实现
3. 如果是 npm 包不存在,告诉我包名,让我决定是装还是换
4. 修完跑 `tsc --noEmit` 必须全绿
5. 不要改 tsconfig 的 paths
Step 5:对于真的需要创建的文件,让 agent 单独提议
如果有 utility 确实需要新建:
你要创建 src/utils/X.ts,先输出:
1. 完整文件内容(含类型)
2. 使用它的所有调用点
3. 为什么不用现成的 lodash / dayjs / 内置 API
等我批准再创建。
强制人工 review,避免它再创造一堆只用一次的”辅助文件”。
预防建议
- 在 CLAUDE.md / AGENTS.md /
.cursorrules写:“导入前必须先grep或find确认目标存在;不能编造路径” - 让 agent 完工前自动跑
tsc --noEmit——把它写进”完成标准” - pre-commit hook 跑
tsc --noEmit,TS2307 / TS2305 直接拦 - 项目根放一个
docs/utils.md列出常用 helper 和它们的实际路径,喂给 agent 当上下文 - 给 agent 装 file-search 工具时确保它会用——很多 agent 默认不会主动 search 文件结构
- 强制小 commit:每次
npm install单独提一个 commit,方便回看”这一周到底装了什么” - 对 monorepo,在 root tsconfig 用
paths别名而不是相对路径,幻觉率明显下降