AI 在 pnpm / Yarn 项目里跑 npm 命令 —— 排查与修复

AI 在 pnpm 仓库里跑 npm install,生成了 package-lock.json,破坏锁文件契约、把 CI 弄崩。需要锁死包管理器。

你两年前就定了用 pnpm。仓库里是 pnpm-lock.yaml,CI 用 pnpm install --frozen-lockfile,Dockerfile 里 corepack enable && corepack prepare pnpm@9。Cursor 或 Claude Code 不管这些,它跑 npm install lodash,在 pnpm-lock.yaml 旁边生成一个 package-lock.json,hoist 方式与 pnpm 严格模式不一致,然后把两个都 commit 进去。CI 崩了,因为 pnpm workspace 配置不再匹配 npm 的扁平布局;之前能解析的 phantom dependency 现在失败;同事拉下来得到一坨乱七八糟的 node_modules。这是 AI 编码最常见的”小翻车”,而且会累积——两个锁文件一旦共存,后续每次安装都有风险。

常见原因

按出现频次排序。

1. AI 默认 npm,因为训练数据里 npm 最常见

公开的 Node 教程绝大多数用 npm install,模型对 npm 命令词汇训练深。如果项目没有显式信号,它默认就上 npm。

如何识别:仓库里明明有 pnpm-lock.yamlyarn.lock,AI 还是建议 npm install <pkg>

2. package.json 里缺 packageManager 字段

现代 Node 工具链(corepack)用 package.json 里的 "packageManager": "pnpm@9.x" 声明权威管理器。不写,AI 就没有机器可读的信号。

如何识别:cat package.json | grep packageManager 没输出。

3. AI 跑 npm 是为了”修”一个它误诊的问题

它看到缺依赖的报错,直接 npm install <pkg>,完全没考虑这会留下竞争锁文件——求快压过求对。

如何识别:某个本应修别的问题的 commit 里冒出来一个 package-lock.json

4. 团队成员或环境里混用了多种管理器

一个人 npm,一个人 pnpm,CI 用 yarn。AI 继承这种不一致,根据当前打开文件所在目录看到什么用什么。

如何识别:版本历史里出现过多种锁文件;同事的 node_modules 布局各不相同。

5. AI 生成了绕过 workspace manager 的 npx

npx prisma generate 能跑,但不尊重 pnpm 的 workspace 结构。monorepo 里这会解析到与 workspace 锁定不同的 prisma 版本。

如何识别:AI 生成的脚本里出现 npx <tool>;本地和 CI 的工具版本不一致。

6. AI 给 package.json 加了”兼容性”脚本

有时 AI 会”贴心”地写一句 "scripts": { "install:npm": "npm install" }。任何同事跑这个脚本就会污染锁文件。

如何识别:package.json 的 scripts 里同时引用了 npm 和 pnpm/yarn。

开始之前

  • 确定本项目的权威包管理器。还不清楚就先和团队定下——统一好就解决了一半。
  • 检查 AI 暂存的改动里有没有 package-lock.json 或多余的 node_modules 路径。
  • 决定强制方式:preinstall 脚本、corepack、CI 检查,三者都上更稳。

需要收集的信息

  • 仓库里都有哪些锁文件:ls -la *.lock* *lock*.yaml *lock*.json
  • package.json 内容——特别是 packageManager 字段与 engines
  • .npmrc.pnpmrc.yarnrc.yml(如有)。
  • CI 配置:流水线里用的是哪条 install 命令。
  • Dockerfile / devcontainer 启动时做了什么。
  • 出现错误锁文件或同时存在多种锁文件的近期 commit。

分步修复

按”先清当前烂摊子,再防再犯”排序。

第 1 步:删掉错误锁文件并按规重装

如果权威管理器是 pnpm,而冒出来了 package-lock.json:

rm -rf node_modules package-lock.json
pnpm install
git add package.json pnpm-lock.yaml
git rm package-lock.json 2>/dev/null

权威是 yarn 或 npm 的话,把命令对应替换即可。

第 2 步:在 package.json 里声明权威管理器

{
  "packageManager": "pnpm@9.7.0",
  "engines": {
    "node": ">=20",
    "pnpm": ">=9"
  }
}

启用 corepack(corepack enable)后,在这个仓库里跑 npm install 会警告或失败。

第 3 步:加 preinstall 守卫

package.json 里:

{
  "scripts": {
    "preinstall": "npx only-allow pnpm"
  }
}

only-allow 是一个极小的包,用错管理器它会直接 abort。报错信息很明确:“Use pnpm, not npm.”

第 4 步:CI 加上重复锁文件检查

CI 里:

if [ -f "package-lock.json" ] || [ -f "yarn.lock" ]; then
  echo "ERROR: Found a non-pnpm lockfile. This repo uses pnpm only."
  exit 1
fi

带错误锁文件的 PR 直接合并失败。

第 5 步:在规则文件里显式告诉 AI

.cursorrulesCLAUDE.md:

This repo uses pnpm. Never use npm or yarn commands.

- Install: pnpm install
- Add package: pnpm add <pkg>
- Add dev dep: pnpm add -D <pkg>
- Remove: pnpm remove <pkg>
- Run script: pnpm run <script> or pnpm <script>
- Workspaces: pnpm --filter <pkg> <cmd>

Do NOT generate or run:
- npm install / npm ci / npm add
- yarn install / yarn add
- npx (use pnpm dlx instead if needed)

The preinstall hook will block npm/yarn anyway, but do not generate them.

直接把策略放进 AI 的工作上下文。

第 6 步:用 pnpm dlx / yarn dlx 替代 npx

一次性工具调用:

# 别用:
npx prisma generate

# 用:
pnpm dlx prisma generate
# 或者更好——固化成 dev dep 再用:
pnpm prisma generate

pnpm dlx 尊重 workspace 解析器。附赠:因为 pnpm 用 content-addressed store,它一般比 npx 还快。

第 7 步:用 corepack 锁住开发环境

README 与 Dockerfile 里都加上:

corepack enable
corepack prepare pnpm@9.7.0 --activate

这样每个开发环境和 CI runner 都是同一个 pnpm 版本。哪怕 AI 想”用 pnpm 试一下”也不会用到错版本。

验证

  • 仓库里只剩一种锁文件(比如只有 pnpm-lock.yaml)。
  • 在仓库里跑 npm install 立即被 only-allow 拦下。
  • 带错误锁文件的 PR 在 CI 里失败。
  • 让 AI”加一个新包”,第一次就给出 pnpm add(不是 npm install)。
  • 全新克隆 + pnpm install --frozen-lockfile 能产出可用的 node_modules

长期预防

  • 新仓库一律设 packageManager。这是最有效的单点护栏。
  • preinstall 拦住错管理器——人和 AI 一起抓。
  • CI 既要检查重复锁文件,也要监控 package-lock.json 是否突然增大(意外的 npm install 信号)。
  • 通过规则文件一次性教育 AI,不要靠每次 prompt 内提醒。
  • monorepo 优先 pnpm 或 yarn berry——npm workspaces 最弱,AI 的默认偏好对它伤害最大。
  • 通过 corepack 锁版本号(不只是名字),避免漂移。

常见误区

  • 不设 packageManager,寄希望于”反正 pnpm-lock.yaml 就在那 AI 应该看得到”——它看不到。
  • 删了 package-lock.json 却忘记加 preinstall 守卫——下一次会话 AI 又给你生成出来。
  • AI 生成的脚本里用 npx 调 monorepo 工具——会静默拿到错版本。
  • 容忍某个同事”我就用 npm,pnpm 烦死人”——他们提交错锁文件,AI 之后会沿着他们的 commit 模仿。
  • 两个锁文件都提交”以防万一”——一定会分歧,一定有一个是错的。
  • CI 没开 corepack,导致 CI 的 pnpm 版本和本地不一样——症状和 AI 用错管理器几乎相同。

相关问题见 AI 锁文件冲突AI 覆盖了环境变量AI 代码本地通过但云端构建失败

FAQ

Q:pnpm-lock.yaml 明明就在那,AI 为什么还是默认用 npm?

公开训练数据里 npm 占主导。锁文件的存在是 AI 可能注意到也可能忽略的弱信号,packageManager 字段才是强信号——务必设上。

Q:能不能 npm 和 pnpm 共用一个 package.json?

不行。它们 hoist 策略不同、peer dep 解析不同,产出的 node_modules 互不兼容。选一种,锁死。

Q:想从 npm 迁到 pnpm,该怎么 prompt AI?

“本项目正在从 npm 迁到 pnpm。请删 package-lock.jsonnode_modules,跑 pnpm import 转换锁文件。从现在起只用 pnpm。在 package.json 里加 packageManager: pnpm@9。”

Q:preinstall 守卫离线开发会不会出问题?

only-allow 是个本地小脚本,不需要联网。它只看 npm_config_user_agent 环境变量来判断,离线照样能用。

标签: #排查 #AI 编程 #npm #pnpm #yarn #package-manager