Codex Added a Package but the Lockfile Did Not Change

Codex edits package.json but never ran npm install, so package-lock.json diverges. How to enforce lockfile updates via setup.sh, CI, and AGENTS.md.

You review the Codex PR. package.json now lists zod as a new dependency. The code imports from zod. Everything looks fine — until CI runs npm ci and fails: “lockfile out of sync with package.json.” Codex added the dep to package.json but never ran npm install, so package-lock.json does not contain zod. Worse variants: the agent ran npm install --no-save, or it edited the lockfile by hand and wrote a fake hash.

This is a small surface area but breaks npm ci, breaks reproducible builds, and risks the agent on the next run picking a different resolved version than your teammates have locally.

Common causes

1. Agent skipped the install step

The Codex sandbox added "zod": "^3.22.0" to package.json and moved on. It never executed npm install. The lockfile is untouched.

How to spot it: git diff shows package.json changed but package-lock.json / pnpm-lock.yaml / yarn.lock did not. npm ci fails.

2. Agent ran npm install --no-save

The model thought it was being cautious — install for testing, do not modify files. The package is in node_modules for the agent’s tests but never in the lockfile.

How to spot it: Transcript shows npm install <pkg> --no-save or npm install --no-save. Lockfile is unchanged.

3. Agent ran the wrong package manager

Your repo uses pnpm but Codex ran npm install, which generates package-lock.json while you ship pnpm-lock.yaml. Now you have both lockfiles and no clear source of truth.

How to spot it: New package-lock.json appears next to existing pnpm-lock.yaml. CI of either tool fails.

4. Agent hand-edited the lockfile

The model saw a “lockfile out of sync” error in a previous run, decided to fix it by editing package-lock.json directly. The shasums are now fake / wrong; npm ci fails on integrity check.

How to spot it: package-lock.json diff is large and looks plausible, but npm ci errors with “integrity checksum failed” or “EINTEGRITY.”

5. setup.sh runs install but does not commit the changes

Codex’s .codex/setup.sh runs npm install to set up the environment, but the resulting lockfile changes never make it into the commit because they happen before the agent’s edit phase.

How to spot it: Lockfile diff exists only locally in the agent sandbox, not in the PR. Agent transcript shows install succeeded.

Shortest path to fix

Step 1: AGENTS.md “always install after package.json change” rule

## Dependency rules

After any change to `package.json` (add, remove, version bump):

1. Run the install command for the active package manager:
   - `npm install` if `package-lock.json` exists
   - `pnpm install` if `pnpm-lock.yaml` exists
   - `yarn install` if `yarn.lock` exists
2. Stage both `package.json` and the lockfile in the same commit.
3. Do not run `npm install --no-save`.
4. Do not hand-edit any lockfile. If the lockfile seems wrong, delete it
   and re-run `npm install` so the manager regenerates it.
5. Do not introduce a second lockfile. Use only the one already in the repo.

In the PR description, list new packages and their reasons.

A short rule with the exact commands the agent should run.

Step 2: Bake install into setup.sh so changes are picked up

Make sure .codex/setup.sh always runs install in a way that surfaces lockfile changes:

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

# Detect package manager
if [ -f pnpm-lock.yaml ]; then
  pnpm install --frozen-lockfile=false
elif [ -f yarn.lock ]; then
  yarn install
else
  npm install
fi

# Show the agent which files changed during setup so it knows to commit them
git status --porcelain

Then in AGENTS.md:

After setup completes, run `git status` and commit any lockfile changes
along with your task changes.

Step 3: CI check that lockfile matches package.json

For 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: Verify lockfile is in sync
        run: npm ci
      - name: Verify no extra lockfiles
        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

npm ci fails if package.json and package-lock.json are out of sync. Required-status-check this and Codex cannot land without updating the lockfile.

For pnpm:

- name: Verify lockfile is in sync
  run: pnpm install --frozen-lockfile

Step 4: “Show lockfile diff in PR” rule

AGENTS.md:

When a PR changes any lockfile, include this section in the PR body:

## Lockfile changes

Run `git diff --stat package-lock.json` (or pnpm-lock.yaml) and paste the
output. Then:

- List packages added
- List packages removed
- List packages whose major version changed
- For each, link the changelog / release notes

Forces the agent to surface dependency churn for reviewers.

Step 5: Block hand-edited lockfiles

Add a pre-commit hook that flags suspicious lockfile changes:

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
  # If lockfile changes, package.json must also change (unless it is a full 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

Catches the “agent edited the lockfile to silence an error” anti-pattern.

Step 6: Pin the package manager via packageManager field

In package.json:

{
  "packageManager": "pnpm@9.0.0"
}

And in .codex/setup.sh, enable corepack so the right manager runs:

corepack enable
corepack prepare --activate

Now Codex cannot accidentally use a different manager and generate a foreign lockfile.

Prevention

  • AGENTS.md mandates install after any package.json edit and which manager to use
  • .codex/setup.sh runs install and surfaces lockfile changes via git status
  • CI runs npm ci / pnpm install --frozen-lockfile as a required check
  • Pre-commit hook flags lockfile changes without corresponding package.json change
  • Pin packageManager in package.json; enable corepack in setup.sh
  • PR body requires a “Lockfile changes” section listing added/removed/bumped packages

Tags: #Codex #agent #Troubleshooting #lockfile