你 review Codex 的 PR:package.json 加了 zod,代码 import 了 zod,看着没问题——直到 CI 跑 npm ci 直接挂:“lockfile out of sync with package.json”。Codex 把 dep 加进 package.json 之后没 npm install,package-lock.json 里压根没 zod。更糟的变种:agent 跑了 npm install --no-save,或者手编辑 lockfile 写了个假 hash。
影响面不大但会破 npm ci、破可重现构建、还有风险是 agent 下次跑时挑了和你队友本地不同的 resolved 版本。
常见原因
1. Agent 跳过了 install 步骤
Codex sandbox 在 package.json 加了 "zod": "^3.22.0" 就走了,从没执行 npm install。Lockfile 一行没动。
如何判断:git diff 显示 package.json 变了,package-lock.json / pnpm-lock.yaml / yarn.lock 没变。npm ci 挂。
2. Agent 跑了 npm install --no-save
Model 以为自己很谨慎——装上测试用,不改文件。包在 agent 的 node_modules 里,但永远不进 lockfile。
如何判断:transcript 里有 npm install <pkg> --no-save 或 npm install --no-save。Lockfile 没变。
3. Agent 用错了 package manager
你 repo 用 pnpm,Codex 跑了 npm install,生成 package-lock.json,而你出 pnpm-lock.yaml。现在两个 lockfile 并存,没明确 source of truth。
如何判断:在已有 pnpm-lock.yaml 旁边冒出新的 package-lock.json。两边 CI 都挂。
4. Agent 手编辑了 lockfile
Model 上次 run 看到 “lockfile out of sync” 错,决定直接改 package-lock.json。shasum 是假的;npm ci 在 integrity check 那挂。
如何判断:package-lock.json diff 很大、看着像那么回事,但 npm ci 报 “integrity checksum failed” 或 “EINTEGRITY”。
5. setup.sh 跑了 install,但变化没 commit
.codex/setup.sh 跑 npm install 来准备环境,但产生的 lockfile 变化在 agent 进入编辑阶段前就发生了,没进 commit。
如何判断:agent sandbox 本地有 lockfile diff,PR 里没有。Transcript 显示 install 成功。
最短修复路径
Step 1:AGENTS.md “改完 package.json 必装” 规则
## 依赖规则
任何对 `package.json` 的改动(增、删、bump 版本)后:
1. 跑当前 package manager 的 install:
- 有 `package-lock.json`:`npm install`
- 有 `pnpm-lock.yaml`:`pnpm install`
- 有 `yarn.lock`:`yarn install`
2. 把 `package.json` 和 lockfile 一起 stage 进同一个 commit。
3. 不许跑 `npm install --no-save`。
4. 不许手编辑 lockfile。lockfile 看起来不对就删掉,再跑一次 install
让 manager 重新生成。
5. 不许再加第二个 lockfile。只用 repo 里现有那个。
PR 描述里列出新增包及其原因。
短规则,附上 agent 应跑的具体命令。
Step 2:setup.sh 跑 install,让变化暴露出来
.codex/setup.sh 永远跑 install,并暴露 lockfile 变化:
#!/usr/bin/env bash
set -euo pipefail
# 检测 package manager
if [ -f pnpm-lock.yaml ]; then
pnpm install --frozen-lockfile=false
elif [ -f yarn.lock ]; then
yarn install
else
npm install
fi
# 让 agent 看见 setup 改了哪些文件,提醒它一起 commit
git status --porcelain
然后 AGENTS.md 里:
setup 完成后,跑 `git status`,把 lockfile 变化和你的任务改动一起 commit。
Step 3:CI 检查 lockfile 与 package.json 同步
npm:
# .github/workflows/lockfile-check.yml
name: Lockfile check
on: [pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- name: 验证 lockfile 同步
run: npm ci
- name: 验证没有多余 lockfile
run: |
count=$(ls -1 package-lock.json pnpm-lock.yaml yarn.lock 2>/dev/null | wc -l)
if [ "$count" -gt 1 ]; then
echo "多个 lockfile 同时存在"
ls -la package-lock.json pnpm-lock.yaml yarn.lock 2>/dev/null
exit 1
fi
package.json 和 package-lock.json 不同步时 npm ci 挂。设 required status check,Codex 不更新 lockfile 就过不了。
pnpm:
- name: 验证 lockfile 同步
run: pnpm install --frozen-lockfile
Step 4:「PR 显示 lockfile diff」规则
AGENTS.md:
PR 改动了任何 lockfile 时,PR body 里加这一节:
## Lockfile 变化
跑 `git diff --stat package-lock.json`(或 pnpm-lock.yaml)贴输出。然后:
- 新增的包
- 删除的包
- 主版本变化的包
- 每个都链对应 changelog / release notes
强制 agent 把依赖变动暴露给 reviewer。
Step 5:拦手编辑 lockfile
pre-commit hook 标可疑 lockfile 改动:
mkdir -p .git/hooks
cat > .git/hooks/pre-commit <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
if git diff --cached --name-only | grep -qE '(package-lock\.json|pnpm-lock\.yaml|yarn\.lock)$'; then
# lockfile 改了,package.json 必须也改(除非是整体 regen)
if ! git diff --cached --name-only | grep -q 'package\.json$'; then
echo "WARNING: lockfile 改了但 package.json 没变。"
echo "故意的(如 dedupe)就 LOCKFILE_REGEN=1 再试。"
[ "${LOCKFILE_REGEN:-0}" = "1" ] || exit 1
fi
fi
EOF
chmod +x .git/hooks/pre-commit
抓 “agent 改 lockfile 来掩盖错误” 的反模式。
Step 6:用 packageManager 字段固定包管理器
package.json:
{
"packageManager": "pnpm@9.0.0"
}
.codex/setup.sh 里开 corepack,正确的 manager 才会跑:
corepack enable
corepack prepare --activate
Codex 没法不小心用别的 manager 而生成异类 lockfile。
预防
- AGENTS.md 强制
package.json改完必装、并明确用哪个 manager .codex/setup.sh跑 install,用git status暴露 lockfile 变化- CI 跑
npm ci/pnpm install --frozen-lockfile当 required check - pre-commit hook 拦 “lockfile 变了但 package.json 没变”
package.json里 pinpackageManager;setup.sh 里开 corepack- PR body 要求一节 “Lockfile 变化” 列出 新增/删除/bump 的包