AI 凭空引用了不存在的文件或包:原因 + 修复路径

Agent 写了 `src/utils/superhelper.ts` 或一个从未存在的包。先列出所有坏 import,再逐个修复或替换。

Build 报 Cannot find module './utils/superhelper',你在项目里 grep 一圈完全没这个文件——Claude Code、Cursor 或 Codex 凭空写了一条 import,引用了它”觉得应该存在”的 utility。它根据训练数据 + 类似项目的常见命名,“补全”了一个看起来很合理的路径,但实际并没创建对应文件。

最快修法:跑 npx tsc --noEmit(或 tsc --build)拿到每一条坏 import 的精确行号,然后逐条处理——要么指向项目里已有的等价实现,要么让 agent 真的把文件建出来。每条 import 只有两种正确结局:实现它,或换掉它。

一个重要例外:如果缺的是 npm 包(不是本地文件),不要盲目 npm install AI 写的那个名字。幻觉出来的包名如今是一种正在被利用的供应链攻击手段(“slopsquatting”,投毒抢注)。先确认这个包真实存在且有人维护。下文细说。

常见原因

按命中率从高到低排序。

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.tssrc/lib/utils.tssrc/helpers/format.ts,于是默认你的项目也有。typescript 脚手架、Next.js / shadcn/ui 默认会带 lib/utils.ts(里面有 cn() 这个 helper),所以哪怕你从没跑过 npx shadcn init,模型也会默认它存在。

如何判断:搜常见模板路径——src/lib/utilssrc/utils/cn@/lib/db——看是不是你压根没建过的”标配”路径。

3. 训练数据里见过的包名(以及 “slopsquatting”)

它写 import { useDebounce } from 'react-use',但你装的是 usehooks-ts,没装 react-use。错误是 Cannot find module 'react-use'。这其实是包幻觉,不是文件幻觉,但症状相同。

这一类有真实风险。USENIX Security 2025 一项研究分析了 16 个模型、57.6 万份代码样本,发现 生成的包依赖里有 19.7% 是幻觉出来的(商业模型约 5.2%,开源模型约 21.7%),而且 58% 的假包名会在多次运行里重复出现。攻击者就专门去 npm / PyPI 抢注这些可预测的假名字,于是下一个照搬 AI 建议去 install 的人就会装到恶意包。已有记录的真实案例包括 react-codeshift(把真实的 jscodeshiftreact-codemod 拼成的假名)和 unused-imports(抢注真实包 eslint-plugin-unused-imports 的名)。所以遇到陌生包名要当成”可疑”,而不是当成一个随手放行的拼写错误。

如何判断:检查 package.jsonnpm ls <package>(或 npm list)没结果就说明从没装过这个包。装之前先看下面的”安装前先验证这个包是不是真的”。

4. Agent 在 monorepo 里走错 workspace

你的 monorepo 里 packages/ui/src/Button.tsx 存在,但 agent 在 packages/app 里写 import { Button } from '../ui/Button',相对路径算错了。文件存在,但路径解析不到。

如何判断:错误信息里的路径是相对路径而且层级看着可疑(../../../ui/),就检查 tsconfig 的 paths / references 设置和实际目录层级。

5. 大小写错误(macOS 本地 OK,CI 挂)

import './Button' 但文件叫 button.tsx。macOS 和 Windows(大小写不敏感的文件系统)能解析,Linux 上的 CI build 直接挂。严格来说不是幻觉,但症状一样,而且是”本地能跑、CI 挂”的头号原因之一。

如何判断git ls-files | grep -i button 看磁盘上的实际大小写。在 tsconfig 里设 "forceConsistentCasingInFileNames": true(TS 5.x 默认开)能让这种问题在本地就暴露,而不是只在 CI 才炸。

6. Agent 引用了它”准备创建”但忘了创建的文件

它在 plan 里说”我会创建 src/api/posts.ts”,但实际只 import 没创建——可能是上下文窗口在中途截断,或者它跳过了那一步。

如何判断:grep agent 这轮所有的 Write / Edit 工具调用,看 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'

TS2307 表示模块 / 文件根本找不到。TS2305 表示文件找到了,但它并没导出那个名字(agent 编造了一个导出,或名字写错了)。每一条对应一个待修。如果你不用 TypeScript,ESLint 的 import/no-unresolved 规则,或者直接跑构建(vite buildnext build)也能给你同样的清单。

Step 2:判断这个东西该不该存在

打开报错的那个文件,看它怎么用这个 import:

import { superhelper } from '@/utils/superhelper';

// ...
const result = superhelper(data, { format: 'json' });

问自己两个问题:

  1. 这个功能项目里已经有等价物吗?(如 lodashdayjs、你自己写过的 helper)
  2. 如果没有,值得为它建一个新文件吗?
情况修法
项目里已有等价物换 import,不建新文件
是个小功能(< 20 行直接 inline 写在调用处
是个能被多处复用的工具真的把文件建出来
本地路径拼错 / monorepo 路径算错修相对路径,或改用 tsconfig 别名
大小写不一致(只在 CI 报 TS2307)把 import 改成跟磁盘上完全一致的大小写
缺 npm 包先验证它是真包(见下节),再装真包或换成已装的等价包

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:安装前先验证这个包是不是真的

如果缺的 import 是个包,在 npm install 之前先做这一步。幻觉出来的名字可能根本没注册(顶多是个无害的构建报错),但更糟的情况是它已经被攻击者抢注了。

# 它在 registry 上存在吗?有多少人用?
npm view react-use         # 404 = 不存在;否则会显示版本 / 维护者
npm view react-use time    # 看首次发布时间——刚发布不久 + 跟 AI 写的名字一模一样,是危险信号

加进项目前先做几个 sanity check:

  • 周下载量 / 仓库:在 npmjs.compypi.org 上打开这个包。真实工具会有 GitHub 仓库、有历史、下载量不会太低。一个”听起来很主流”的名字却只有几十的周下载量,很可疑。
  • 名字拼接:AI 给的名字是不是两个真实包的混搭(比如 react-codeshift = jscodeshift + react-codemod)?把两半分别搜一下——真包通常是其中之一。
  • 发布者:信任发布者和历史记录,而不是名字本身。slopsquatting 靠的就是你看到一个像模像样的名字就放行。

拿不准就直接搜 <生态> 里做 X 用什么包,自己装那个公认的包,而不是照搬 AI 吐出来的字符串。

Step 5:让 agent 自己修,给精确 prompt

不要手动修每一个——把列表喂回 agent:

build 失败。以下 import 引用了不存在的文件或包:

[贴 tsc --noEmit 的输出]

修复规则(严格遵守):
1. 不要创建新文件,除非项目里真的没有等价实现
2. 修每个 import 前先 grep 项目,确认是否有现成实现
3. 任何缺失的 npm 包,停下来,把包名 + npm 周下载量告诉我。
   由我决定是装还是换。不要自己跑 npm install。
4. 修完跑 `tsc --noEmit` 必须全绿
5. 不要改 tsconfig 的 paths

Step 6:对于真的需要创建的文件,让 agent 单独提议

如果有 utility 确实需要新建:

你要创建 src/utils/X.ts,先输出:
1. 完整文件内容(含类型)
2. 使用它的所有调用点
3. 为什么不用现成的 lodash / dayjs / 内置 API

等我批准再创建。

强制人工 review,避免它再创造一堆只用一次的”辅助文件”。

如何确认真的修好了

按顺序跑:

npx tsc --noEmit            # 必须 exit 0,没有 TS2307 / TS2305
npm ls 2>&1 | grep -i "missing\|invalid"   # 没有缺失 / 多余的依赖
npm run build               # 真正的构建必须通过

本地 tsc 全绿 + 构建干净,才算真的”完成”。如果你只是把编辑器里的红波浪线修没了但 build 还挂,说明你漏了一个 TypeScript 检查不到的路径(比如只在运行时用的 require、资源文件 import,或者只在 Linux CI 上才发作的大小写问题)。在大小写敏感的环境或 CI 上重跑一次构建,把原因 5 兜住。

常见问题

为什么 AI 老是编造 import,而不是承认自己不知道? LLM 是在生成”下一个最可能的 token”,而在 import { formatDate } from 之后,最可能的续写就是一个路径。模型本身没有”这个文件存在吗”的内置检查——除非它是带 file-search 工具的 agent,而且真的去用了。所以你的修复循环应该强制 grep / tsc 校验,而不是靠信任。

直接 npm install 它建议的包名危险吗? 有可能危险。大约每 5 个 AI 建议的包名就有 1 个不存在(USENIX Security 2025),而攻击者会预先抢注那些常见的假名字——这就是 “slopsquatting”。装之前一定先确认这个包真实存在、有人维护、而且确实是你要的那个。先跑 npm view <name>,看它的仓库和下载量。

tsc --noEmit 全绿了但 build 还是挂,为什么? TypeScript 只检查它会类型检查的东西。动态 require()、图片 / CSS / 资源文件的 import、以及 Linux 上只差大小写的路径,都会绕过它。跑真正的构建(next buildvite build 等),最好在大小写敏感的文件系统或 CI 上跑。

怎么区分是文件幻觉,还是大小写 / 路径 bug? 如果 git ls-files | grep -i <名字> 能找到文件,那就是大小写或相对路径 bug(原因 4 和 5),不是幻觉——文件在,只是 import 字符串写错了。如果哪儿都搜不到,那就是从没创建过。

怎么从根上避免每次会话都出这事?CLAUDE.md / AGENTS.md / .cursorrules 里写一条硬规则:“导入前必须 grep / find 确认目标存在;不能编造路径;任何新包都要先停下来问。“把 tsc --noEmit 写进 agent 的完成标准,再加一个 pre-commit hook,让 TS2307 / TS2305 根本提交不上去。

预防建议

  • CLAUDE.md / AGENTS.md / .cursorrules 写:“导入前必须先 grepfind 确认目标存在;不能编造路径;不确认是真包就不许安装。”
  • 让 agent 完工前自动跑 tsc --noEmit——把它写进”完成标准”。
  • pre-commit hook 跑 tsc --noEmitTS2307 / TS2305 直接拦。
  • 项目根放一个 docs/utils.md 列出常用 helper 和它们的实际路径,喂给 agent 当上下文。
  • 给 agent 装 file-search 工具时确保它会用——很多 agent 默认不会主动 search 文件结构。
  • 强制小 commit:每次 npm install 单独提一个 commit,方便回看”这一周到底装了什么、名字是不是真的”。
  • 对 monorepo,在 root tsconfig 用 paths 别名而不是一长串相对路径,幻觉 / 路径算错的概率明显下降。
  • 在 tsconfig 设 "forceConsistentCasingInFileNames": true,让大小写问题在本地就挂掉,而不是只在 CI 挂。

相关阅读

标签: #AI 编程 #排查 #排查