Claude Code 或 Cursor 给你写一个新功能,顺手 npm install lodash 一条命令,package-lock.json 从 5000 行变成 5400 行。你 push 一下,CI 立刻红:“npm ci failed: lockfile out of sync”,或者同事 pull 之后 pnpm install --frozen-lockfile 跑不过。冲突文件一打开是 4000 行 <<<<<< / >>>>>>,手工对根本不可能。
本文给你”丢掉 AI 的脏 lockfile、保留 package.json 的合理新增、用项目指定的包管理器重新生成干净 lockfile”的最短路径,并解释为什么手工 resolve lockfile 是错的。
常见原因
按出现频率排序,前 3 个占 80%。
1. Agent 跑了 npm install foo 而项目用的是 pnpm / yarn
最经典:CLAUDE.md / AGENTS.md 没写包管理器,agent 默认 npm install,但项目用 pnpm。结果生成了一份 package-lock.json,同时 pnpm-lock.yaml 没更新;或者两个 lockfile 都存在,CI 直接拒绝。
ERR_PNPM_OUTDATED_LOCKFILE Cannot install with "frozen-lockfile"
because package-lock.json was modified
如何判断:仓库里突然出现两个 lockfile(package-lock.json + pnpm-lock.yaml),或 lockfile 顶部的版本号变了。
2. AI 改了 package.json 但没重跑 install
Aider 或 Codex 在 package.json 加了 "axios": "^1.7.0",但因为权限或环境问题没真的执行 npm install,lockfile 完全没更新。CI 上 npm ci --frozen-lockfile 立刻挂。
npm error code EUSAGE
npm error `npm ci` can only install packages when your package.json
and package-lock.json are in sync.
如何判断:package.json 里有的依赖在 lockfile 里完全找不到。
3. AI 在 main 已 push 新依赖时同步本地 install
你的分支落后于 main 3 个新依赖。AI 在本地跑 npm install whatever,npm 不止装了 whatever,还把你 lockfile 里其他过时的间接依赖都 hoist 重排了。结果 lockfile 里 1500 行无关变动,rebase 时冲突。
如何判断:lockfile diff 里大量你没装过的包出现增减。
4. AI 以为某个依赖没用就删了,其实是间接依赖
Agent 看 import 没有显式引用就把 tslib / regenerator-runtime / core-js 这类底层包从 dependencies 里删了。本地 dev 没事,prod build 里被传递依赖引用直接挂。
Module not found: Can't resolve 'tslib'
如何判断:删除的包是 tslib、@babel/runtime、core-js、regenerator-runtime 这类底层运行时;或者 npm ls <pkg> 显示有多个父依赖。
5. AI 升级了一个包,触发 peer dep 大范围漂移
让 agent 把 react 从 18 升到 19,它顺手把 lockfile 里几十个 peer dep 一起锁到新版本。其中某个 lib 的 v19 兼容版本还没发,lockfile 锁了 “wanted but not installable”。
如何判断:npm install 报 ERESOLVE could not resolve,或 lockfile 里出现红色 unresolved 节点。
6. 两份 lockfile 同时被改
monorepo 里 root lockfile + workspace lockfile 都存在,AI 只更新了其中一个。
如何判断:pnpm-workspace.yaml 或 package.json 的 workspaces 字段下多个 lockfile 时间戳不一致。
最短修复路径
核心原则:lockfile 永远不要手工 resolve,重新生成才对。
Step 1:把 lockfile 整个回退到 main 的版本
冲突来了别看 4000 行的 <<<<<<,直接:
git checkout origin/main -- package-lock.json
# 或者 pnpm / yarn
git checkout origin/main -- pnpm-lock.yaml
git checkout origin/main -- yarn.lock
monorepo 一次性全清:
git checkout origin/main -- $(git diff --name-only --diff-filter=U | grep -E '(package-lock\.json|pnpm-lock\.yaml|yarn\.lock)$')
这一步把”AI 那份脏 lockfile”彻底丢掉,只保留 package.json 的合理新增。
Step 2:审查 package.json 的真实变动
git diff origin/main -- package.json
逐行问自己三个问题:
- 这个新依赖真的需要吗?(agent 经常装”以防万一”的包)
- 版本号是不是 agent 随便写的
^latest?换成项目惯用的 caret / tilde / pinned 风格 - 移到
dependencies还是devDependencies对了吗?(typescript 类型一定要在 devDeps)
不要的依赖手工从 package.json 删掉。
Step 3:用项目指定的包管理器重新生成 lockfile
先确认项目用什么:
cat package.json | grep packageManager
# 例如 "packageManager": "pnpm@9.1.0"
然后只用对应的命令重新生成:
| 包管理器 | 重新生成命令 |
|---|---|
| npm | npm install --package-lock-only |
| pnpm | pnpm install --lockfile-only |
| yarn (classic) | yarn install --mode=update-lockfile |
| bun | bun install --frozen-lockfile=false |
--*-only 标志保证只更新 lockfile、不真的下载,速度快几十倍且不污染 node_modules。
Step 4:本地用 frozen 模式验证一遍
rm -rf node_modules
pnpm install --frozen-lockfile # 或 npm ci
npm test && npm run build
frozen-lockfile / npm ci 就是 CI 用的命令——本地能过,CI 才能过。
Step 5:如果 Step 4 报 peer dep 错
ERESOLVE could not resolve
While resolving: react-dom@19.0.0
Found: react@18.3.1
回到 package.json 把冲突方一起升级或降级:
# 用 agent 帮你查兼容范围
# 然后手动 pin:
npm pkg set dependencies.react=19.0.0 dependencies.react-dom=19.0.0
pnpm install --lockfile-only
或者用 overrides / resolutions 字段强制解决:
{
"pnpm": {
"overrides": {
"react": "19.0.0"
}
}
}
预防建议
- 在
package.json里固定packageManager字段(如"packageManager": "pnpm@9.1.0"),Node ≥ 16.9 会强制用 Corepack - CLAUDE.md / .cursorrules / AGENTS.md 第一行写”包管理器是 pnpm,禁止
npm install/yarn add” - pre-commit hook 拦截多 lockfile:检测到
package-lock.json+pnpm-lock.yaml同时存在就拒绝 commit - agent 想装新依赖时让它先输出 “我要装 X 用于 Y”,你确认后再执行——
Bash(npm install:*)默认拒绝、白名单按需放 - CI 用
npm ci --frozen-lockfile/pnpm install --frozen-lockfile,任何 lockfile 漂移都立刻挂掉,不让它进 main - 升级类任务用专门的 AI 依赖升级工作流,不要混在 feature 分支里