Codex 加了包但 lockfile 没动:怎么强制同步更新

Codex 改了 package.json 却没跑 npm install,package-lock.json 和真实依赖分叉、npm ci 直接挂。用 setup.sh、CI 同步检查和 AGENTS.md 规则一次性根治。

你 review Codex 的 PR:package.json 新增了 zod,代码也 import 了 zod,看着一切正常——直到 CI 跑 npm ci,直接挂:

npm error `npm ci` can only install packages when your package.json and
package-lock.json or npm-shrinkwrap.json are in sync. Please update your
lock file with `npm install` before continuing.
npm error Missing: zod@3.23.8 from lock file

Codex 把这个 dep 加进了 package.json,但从没跑 npm install,所以 package-lock.json 里压根没有 zod。更糟的几个变种:agent 跑了 npm install --no-save、手动编辑 lockfile 还写了个假的 integrity hash、或者用错了包管理器,在你真正的 lockfile 旁边丢下了第二个 lockfile。

最快修复: 在你自己的机器上 checkout 这个分支,跑项目对应的 install 命令(npm installpnpm installyarn install),把重新生成的 lockfile 和 package.json 一起 commit。这样能立刻清掉飘红的 CI。本文剩下的部分负责让它别在以后每次 Codex 跑的时候重演——靠一条 AGENTS.md 规则、一步 setup 脚本,和一个 required 的 CI 同步检查。

影响面不大,但它会破 npm ci、破可重现构建,还有风险是 Codex 下次跑的时候 resolve 出一个和你队友本地不一样的版本。

你属于哪一种?

diff / CI 日志里的症状最可能的原因跳到
package.json 变了、lockfile 没变;Missing: <pkg> from lock fileagent 跳过了 install原因 1
transcript 里出现 npm install <pkg> --no-saveinstall 跑了但没写 lockfile原因 2
已有 pnpm-lock.yaml 旁边冒出新的 package-lock.json用错了包管理器原因 3
lockfile diff 很大,npm ciEINTEGRITY / integrity checksum failed手动编辑了 lockfile原因 4
agent 日志里 install 成功,但 PR 里 lockfile 没变setup.sh 跑了 install,但改动没 commit原因 5
没人动过的分支上 npm ci 也挂(does not satisfy上游发了 patch,不是 Codex 的锅原因 6

常见原因

1. Agent 跳过了 install 步骤

Codex sandbox 在 package.json 里加了 "zod": "^3.23.0" 就走了,从没执行 npm install,所以 lockfile 一行没动。

如何判断git diff 显示 package.json 变了,而 package-lock.json / pnpm-lock.yaml / yarn.lock 没变。npm ciMissing: <pkg> from lock file

2. Agent 跑了 npm install --no-save

Model 以为自己很谨慎——装上只是为了测试,不改文件。结果包落进了 agent 的 node_modules 供它跑测试,却永远进不了 lockfile。

如何判断:transcript 里有 npm install <pkg> --no-savenpm install --no-save,而 lockfile 没变。

3. Agent 用错了包管理器

你的 repo 用 pnpm,Codex 却跑了 npm install,于是生成了 package-lock.json,而你交付的是 pnpm-lock.yaml。现在两个 lockfile 并存,没有明确的 source of truth。

如何判断:已有 pnpm-lock.yaml 旁边冒出一个新的 package-lock.json。不管 CI 强制的是哪个 lockfile,另一个工具的 install 都会挂。

4. Agent 手动编辑了 lockfile

Model 在上一次 run 看到了同步报错,决定直接改 package-lock.json 来”修好”它。结果 integrity 的 shasum 是它编出来的,npm ci 在 integrity check 那一步就挂了。

如何判断package-lock.json 的 diff 很大、看着像模像样,但 npm cinpm error code EINTEGRITYintegrity checksum failed

5. setup.sh 跑了 install,但改动没 commit

Codex 的 setup 脚本跑了 npm install 来准备容器,但这些 lockfile 改动从没进到 commit 里。按 OpenAI 的 Cloud environments 文档(截至 2026 年 6 月):setup 脚本在 agent 之前、于一个独立的 Bash session 里运行,它装的依赖和文件改动会保留进缓存的 workspace,但 Codex 从不自动 commit 它们。如果 agent 随后改了 package.json 却没重新跑 install,那就只有它自己的改动被 commit。缓存容器还可能在更旧的 commit 上恢复,这时 Codex 会跑那个可选的 “maintenance script” 来刷新依赖——同样地,这一步也不产生 commit。

如何判断:lockfile diff 只存在于 agent sandbox 里,PR 里没有。agent transcript 显示 install 是成功的。

6. 上游发了 patch——不是 Codex 的锅

一个值得排除的真实边缘情况:在 npm 11(截至 2026 年 6 月的当前版本)下,npm ci 可能在没人动过的 lockfile 上也挂。当某个传递依赖的新 patch 版本被发布到 registry 时,npm ci 会按 package.json 里的范围重新 resolve,新 resolve 出来的版本和 lockfile 里 pin 住的对不上,于是报出类似 lock file's @types/node@24.13.1 does not satisfy @types/node@24.13.2 的错。详见 npm/cli 的 issue #8693#8726

如何判断:报错说的是 does not satisfy 而不是 Missing,而且在没有任何 Codex 改动的 main 上也能复现。修法是跑一次 npm install 刷新 lockfile 并 commit——如果反复出现,就把版本范围 pin 得更紧。

最短修复路径

Step 1:AGENTS.md 里加一条”改完 package.json 必装”规则

AGENTS.md(放 repo 根目录)是 Codex 读取项目专属命令和约定的文件。加一条明确的依赖规则:

## 依赖规则

任何对 `package.json` 的改动(增、删、bump 版本)之后:

1. 跑当前包管理器对应的 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 让包管理器重新生成。
5. 不许引入第二个 lockfile。只用 repo 里已有的那个。

在 PR 描述里列出新增的包及每个的理由。

一条带着确切命令的短规则,胜过一整段散文:agent 对命令式、带编号的指令遵守得可靠得多。

Step 2:把 install 写进 setup 脚本,让改动可见

确保你的 Codex setup 脚本总是以能暴露 lockfile 改动的方式跑 install。文档里有个注意点:setup 脚本在自己的 Bash session 里运行,所以你在里面 export 的东西不会带进 agent 阶段(真正要持久化的环境变量请写进 ~/.bashrc 或环境的 secret 设置里)。这个脚本只负责 provisioning,不负责状态。

#!/usr/bin/env bash
set -euo pipefail

# 检测包管理器
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:一个检查 lockfile 与 package.json 同步的 CI

这才是真正的护栏。把它设成 required status check,那么 Codex(或任何人)都没法把一个不同步的 lockfile 合进去。

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 "More than one lockfile present"
            ls -la package-lock.json pnpm-lock.yaml yarn.lock 2>/dev/null
            exit 1
          fi

只要 package.jsonpackage-lock.json 不同步,npm ci 就返回非零退出码。

pnpm 的等价严格安装是:

- name: 验证 lockfile 同步
  run: pnpm install --frozen-lockfile

yarn(Berry)则用 yarn install --immutable

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,而不是把一个 4000 行的 lockfile diff 埋起来。

Step 5:拦住手动编辑的 lockfile

加一个 pre-commit hook,标记可疑的 lockfile 改动(lockfile 变了但 package.json 没变,正是”agent 手改 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 就必须也改(除非是整体 re-gen)
  if ! git diff --cached --name-only | grep -q 'package\.json$'; then
    echo "WARNING: lockfile changed but package.json did not."
    echo "If this is intentional (e.g. dedupe), confirm by setting LOCKFILE_REGEN=1."
    [ "${LOCKFILE_REGEN:-0}" = "1" ] || exit 1
  fi
fi
EOF
chmod +x .git/hooks/pre-commit

.git/hooks 下的本地 hook 不会随 clone 共享,所以团队要用就通过 Husky 或一个 commit 进仓库的 core.hooksPath 目录来配。Step 3 里的 CI 检查才是那个永远会跑的兜底。

Step 6:用 packageManager 字段固定包管理器

package.json 里:

{
  "packageManager": "pnpm@9.0.0"
}

然后在 setup 脚本里开 Corepack,让被 pin 住的那个管理器才是真正运行的那个:

corepack enable
# 可选:在 agent 跑之前先把 pin 住的版本下载缓存好
corepack prepare pnpm@9.0.0 --activate

corepack enable 会装上读取 packageManager 字段的 shim,于是 pnpm 精确解析到 9.0.0。不要写一个不带版本的裸 corepack prepare --activate——它需要一个像 pnpm@9.0.0 这样的 descriptor。有了这一步,Codex 就不可能误用别的管理器、生成一个异类 lockfile。

怎么确认已经修好了

重新生成并 commit lockfile 之后,push 前在本地验证一遍:

rm -rf node_modules
npm ci   # 或:pnpm install --frozen-lockfile
git status --porcelain   # 必须不输出任何内容

npm ci 干净通过、加上 git status 为空,就说明 package.json 和 lockfile 一致、install 可重现。之后 Step 3 的 CI 检查会在每一个未来的 PR 上强制同一套标准。

预防

  • AGENTS.md 强制要求改完 package.json 后必装,并指明用哪个管理器。
  • Codex 的 setup 脚本跑 install,并用 git status 暴露 lockfile 改动。
  • CI 把 npm ci / pnpm install --frozen-lockfile / yarn install --immutable 当作 required check。
  • 一个 pre-commit hook(以及 CI)标记”lockfile 变了但 package.json 没变”的情况。
  • package.json 里 pin packageManager,并在 setup 脚本里开 Corepack。
  • PR body 要求一节”Lockfile 变化”,列出新增 / 删除 / bump 的包。

FAQ

为什么 agent 本地 npm install 成功,CI 的 npm ci 却挂? npm install 会改写 lockfile 去匹配 package.jsonnpm ci 恰好相反——它在两者尚未一致时拒绝安装,然后严格按 lockfile 安装。agent 本地的 npm install 之所以成功,正是因为它会去写 lockfile;但如果那次写入没进到 commit,CI 的 npm ci 看到的就是不一致。

我的 lockfile 是对的,npm ci 却还报 “does not satisfy”,怎么办? 那是原因 6,不是 Codex 的 bug。在 npm 11 下,上游新发布的一个 patch 会让 npm ci 重新 resolve 到一个 lockfile 没 pin 的版本。跑一次 npm install 刷新 lockfile、commit,并考虑把范围 pin 得更紧。可在 npm/cli 的 issue #8693 追踪。

我能不能干脆让 CI 自动跑 npm install 并替我 commit lockfile? 能,但这会把依赖变动从 review 里藏起来,让 agent 的选择不经审计就落地。更好的做法是让 PR 挂掉,逼 agent(或你自己)去 commit 一个深思熟虑的 lockfile,并在 PR body 里附上”Lockfile 变化”那一节。

两个 lockfile 都冒出来时,我该删哪个、留哪个? 删那个 repo 原本没交付的。如果你用 pnpm,就删掉多余的 package-lock.json,再跑 pnpm install 确认 pnpm-lock.yaml 仍然同步。Pin packageManager(Step 6)能防止重复 lockfile 再冒出来。

Codex 会 commit 我 setup 脚本做的改动吗? 不会。截至 2026 年 6 月,setup 脚本在 agent 阶段之前、于一个独立的 Bash session 里运行,它装的依赖和文件改动会保留进缓存的 workspace,但 Codex 只 commit agent 自己的编辑。如果 install 只在 setup 里跑过,就在 agent 阶段再跑一次(照 AGENTS.md 规则),让 lockfile 改动成为 commit 的一部分。

相关

标签: #Codex #agent #排查 #lockfile