最快修复: 删掉多出来的 package-lock.json,用真正的管理器重装,然后在 package.json 里把它钉死(packageManager 加 devEngines.packageManager),再补一条 preinstall: only-allow pnpm 守卫。真正能阻止 AI 再犯的是这个”钉死”动作。下面是完整的清理与锁定流程。
你两年前就定了用 pnpm。仓库里是 pnpm-lock.yaml,CI 跑 pnpm install --frozen-lockfile,Dockerfile 里 corepack enable && corepack prepare pnpm@11。Cursor 或 Claude Code 不管这些,它跑 npm install lodash,在 pnpm-lock.yaml 旁边生成一个 package-lock.json,hoist 方式与 pnpm 的严格(非扁平)布局不一致,然后把两个都 stage 进去。CI 崩了,因为 workspace 不再匹配 npm 的扁平布局;之前能解析的 phantom dependency 现在失败;同事拉下来得到一坨乱七八糟的 node_modules。这是 AI 编码最常见的”小翻车”,而且会累积:两个锁文件一旦共存,后续每次安装都有风险。
先判断你属于哪一类
先跑这个,确保修对地方:
ls -la package-lock.json pnpm-lock.yaml yarn.lock bun.lock 2>/dev/null
grep -E '"packageManager"|"devEngines"' package.json
| 症状 | 可能原因 | 跳到 |
|---|---|---|
package-lock.json 与 pnpm-lock.yaml 并存 | AI 跑了裸 npm install | 第 1 步,再做第 2-3 步 |
| git 历史里两种锁文件,都没钉死 | 缺 packageManager 字段 | 第 2 步 |
| AI 每次会话都重新生成错锁文件 | 没有 preinstall 守卫或规则文件 | 第 3、5 步 |
monorepo 里 npx <tool> 解析到错版本 | npx 绕过了 workspace | 第 6 步 |
| 本地正常,CI 报几乎一样的错 | CI 没启用 corepack | 第 7 步 |
常见原因
按出现频次排序。
1. AI 默认 npm,因为训练数据里它最多
公开的 Node 教程绝大多数用 npm install,模型对 npm 命令词汇训练得很深。如果项目没有显式信号,它默认就上 npm。
如何识别: 仓库里明明有 pnpm-lock.yaml 或 yarn.lock,AI 还是建议 npm install <pkg>。
2. package.json 里缺 packageManager 字段
corepack(Node 16.9+ 自带)读 package.json 里的 "packageManager": "pnpm@11.x" 来决定权威管理器。不写,AI 没有机器可读的信号,corepack 也没有。
如何识别: grep packageManager package.json 没输出。
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路径。 - 决定强制方式:
devEngines/packageManager(corepack)、preinstall守卫、CI 检查。共享仓库三者都上。
需要收集的信息
- 仓库里都有哪些锁文件:
ls -la package-lock.json pnpm-lock.yaml yarn.lock bun.lock。 package.json内容,特别是packageManager、devEngines与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 --cached package-lock.json 2>/dev/null
权威是 yarn 或 npm 的话,把命令对应替换即可。再把 package-lock.json(或那个外来的锁文件)加进 .gitignore,以后就不会再被 stage。
第 2 步:在 package.json 里声明权威管理器
两个字段都写。截至 2026 年 6 月,corepack 读 packageManager 字段来锁定精确版本;devEngines.packageManager(pnpm v11 新增)让你声明一个版本区间和失败行为,而且是目前官方推荐的强制方式——因为老的 only-allow 仓库现在已经归档。
{
"packageManager": "pnpm@11.7.0",
"devEngines": {
"packageManager": {
"name": "pnpm",
"version": ">=11.0.0 <12.0.0",
"onFail": "error"
}
},
"engines": {
"node": ">=22"
}
}
2026 年 6 月需要注意:
- pnpm 11 要求 Node 22+,而且现在是纯 ESM,所以把
engines.node设成>=22对齐。当前稳定版是 11.7.0。 - 启用 corepack(
corepack enable)后,在这个仓库里跑yarn或错版本的pnpm会被重定向或拒绝,以匹配钉死的版本。 onFail: "error"让版本不匹配时直接硬失败,而不是悄悄继续。可以用pmOnFail设置按机器覆盖,无需改 manifest。- corepack 默认会自动更新
packageManager字段;若想保持你写的原样,设COREPACK_ENABLE_AUTO_PIN=0。
第 3 步:加 preinstall 守卫
package.json 里:
{
"scripts": {
"preinstall": "npx only-allow pnpm"
}
}
only-allow 是一个极小的包,它检查 npm_config_user_agent 环境变量,用错管理器就 abort。跑 npm install 会打印明确报错并以非零码退出。
重要限制(2026 年 6 月核实):preinstall 钩子只在 npm install / npm ci 时触发,它拦不住 npm install <package>(安装单个包),因为 npm 会在跑 preinstall 之前就解析并写出 package-lock.json。而这恰恰是 AI 最爱跑的命令。所以 only-allow 是兜底,不是唯一防线;第 2 步的 devEngines 钉版本和第 4 步的 CI 检查负责补这个缺口。(only-allow 的 GitHub 仓库 2026 年已归档但仍可用,官方推荐的替代品就是 devEngines.packageManager。)
第 4 步:CI 加上外来锁文件检查
在 CI 里、install 之前跑这个,这样即使本地守卫被绕过,坏 PR 也合不进来:
if [ -f "package-lock.json" ] || [ -f "yarn.lock" ] || [ -f "bun.lock" ]; then
echo "ERROR: Found a non-pnpm lockfile. This repo uses pnpm only."
exit 1
fi
这才是真正能抓住 npm install <package> 的检查——那条命令尽管有 preinstall 守卫,还是会生成 package-lock.json。
第 5 步:在规则文件里显式告诉 AI
写进 .cursor/rules/package-manager.mdc(Cursor 当前的格式;旧的 .cursorrules 仍然可用)或 CLAUDE.md:
This repo uses pnpm 11. 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 install <pkg>
- yarn install / yarn add
- npx (use pnpm dlx instead if needed)
The preinstall hook and devEngines pin will block the wrong manager,
but do not generate npm/yarn commands in the first place.
这把策略直接放进 AI 的工作上下文——这一层才能从根上不让错命令被建议出来。
第 6 步:用 pnpm dlx / yarn dlx 替代 npx
一次性工具调用:
# 别用:
npx prisma generate
# 用:
pnpm dlx prisma generate
# 或者更好——固化成 dev dep 再用:
pnpm exec prisma generate
pnpm dlx 尊重 workspace 解析器。而且因为 pnpm 用 content-addressed store,它通常比 npx 还快。
第 7 步:用 corepack 锁住开发环境
README、Dockerfile 和 CI 配置里都加上:
corepack enable
corepack prepare pnpm@11.7.0 --activate
这样每个开发环境和 CI runner 都是同一个 pnpm 版本(由 packageManager 字段推导)。哪怕 AI 想”用 pnpm 试一下”也不会用到错版本。如果只在本地启用 corepack、CI 不启用,就会出现一类症状和”用错管理器”几乎一模一样的 bug。
如何确认已修好
- 仓库里只剩一种锁文件(比如只有
pnpm-lock.yaml)。 - 在仓库里跑
npm install立即被only-allow拦下。 - 跑
npm install <某个包>不会留下被提交的package-lock.json,因为 CI 会拒绝它(单靠 preinstall 守卫抓不到这种情况)。 corepack prepare pnpm@11.7.0 --activate && pnpm --version打印出钉死的版本。- 让 AI”加一个新包”,第一次就给出
pnpm add(不是npm install)。 - 全新克隆 +
pnpm install --frozen-lockfile能产出可用的node_modules。
长期预防
- 新仓库一律设
packageManager(pnpm 11+ 再加devEngines.packageManager)。这是最有效的单点护栏。 - 把 CI 锁文件检查当成真正的强制层,因为
preinstall守卫抓不到npm install <pkg>。 - 通过规则文件一次性教育 AI,不要靠每次 prompt 内提醒。
- monorepo 优先 pnpm 或 yarn berry。npm workspaces 最弱,AI 的默认偏好在这里伤害最大。
- 通过 corepack 锁版本号(不只是名字),避免漂移。
常见误区
- 不设
packageManager,寄希望于”反正pnpm-lock.yaml就在那,AI 应该看得到”——它看不到。 - 只靠
only-allow,然后惊讶于npm install lodash居然还是写出了package-lock.json。补上 CI 检查。 - 删了
package-lock.json却忘了加预防层,下一次会话 AI 又给你生成出来。 - AI 生成的脚本里用
npx调 monorepo 工具,会静默拿到错版本。 - 容忍某个同事”我就用 npm,pnpm 烦死人”:他们提交错锁文件,AI 之后会沿着他们的 commit 模仿。
- 两个锁文件都提交”以防万一”:一定会分歧,一定有一个是错的。
- 升级到 pnpm 11 后还把
engines.node设成旧区间——pnpm 11 要求 Node 22+。
相关问题见 AI 锁文件冲突、AI 覆盖了环境变量、AI 代码本地通过但云端构建失败。官方文档见 pnpm package.json 参考 和 corepack。
FAQ
Q:pnpm-lock.yaml 明明就在那,AI 为什么还是默认用 npm?
公开训练数据里 npm 占主导。锁文件的存在是 AI 可能注意到也可能忽略的弱信号,packageManager 字段才是更强、机器可读的信号——务必设上。
Q:我加了 only-allow 的 preinstall 守卫,但 npm install lodash 还是生成了 package-lock.json,为什么?
npm 会在跑 preinstall 脚本之前就解析并写出锁文件,所以守卫在 npm install <pkg> 上触发得太晚。它只能可靠拦住裸 npm install / npm ci。用 CI 锁文件检查(第 4 步)和 devEngines 钉版本(第 2 步)补这个缺口。
Q:该用 packageManager 还是新的 devEngines.packageManager?
两个都用。packageManager 钉死 corepack 跑的精确版本;devEngines.packageManager(pnpm v11+,2026 年新增)让你声明版本区间和 onFail 行为,是已归档的 only-allow 仓库的官方推荐继任者。
Q:怎么让 AI 帮我从 npm 迁到 pnpm?
“本项目正在从 npm 迁到 pnpm 11。请删 package-lock.json 和 node_modules,跑 pnpm import 转换锁文件。从现在起只用 pnpm。在 package.json 里加 packageManager: pnpm@11.7.0、一个 devEngines.packageManager 的 pnpm 区间,以及 engines.node: >=22。”
Q:preinstall 守卫离线开发会不会出问题?
only-allow 是个本地小脚本,不需要联网。它只看 npm_config_user_agent 环境变量来判断是哪个管理器调用的,离线照样能用。