ReviewOS

pantry-pm/pantry

--frozen-lockfile rejects a lock the tool just wrote, on a workspace with a companion deps file

#233
Closed chris opened this 8 days ago ·
8 days ago

On a project that has both npm workspaces and a companion deps.yaml, pantry install --frozen-lockfile fails against a lockfile pantry install produced moments earlier:

Error: lockfile is out of date (--frozen-lockfile)

The generated lock is missing the npm entries the committed one has:

only in committed: is-odd@3.0.1
only in committed: is-number@7.0.0

Reproduction

ws/
  package.json      { "workspaces": ["packages/*"], "dependencies": { "is-odd": "3.0.1" } }
  packages/a/package.json  { "dependencies": { "is-number": "7.0.0" } }
  deps.yaml         dependencies: { ccache.dev: ^4 }
pantry install                    # writes pantry.lock
pantry install --frozen-lockfile  # rejects the lock it just wrote

Which versions

0.11.55 (released)accepts
main (unmodified)rejects

Checked by stashing an unrelated in-progress change, rebuilding from clean main, and re-running — the rejection is present without it. So this arrived between the 0.11.55 tag and now.

Where to look

core.zig compares the committed lock against a staged generated one when a workspace has a companion deps file (staged_lockfile_path). The workspace installer stages its lock, and the companion step merges system packages into that staged file. In frozen mode the staged lock ends up without the npm entries — so the comparison sees the committed lock as ahead and calls it out of date, when nothing has actually changed.

The likely cause is the workspace installer taking a lockfile-first fast path in frozen mode and not populating the generated lock with npm entries it did not have to resolve.

Why it matters

--frozen-lockfile is the CI mode. A project of this shape — npm workspaces plus system dependencies, which is what the companion file exists for — cannot currently gate CI on its lockfile at all against main.

Found while fixing #231; unrelated to that change, and deliberately left alone there.

8 days ago

Fixed in 00d26c1, released as 0.11.57.

The cause is one early return. That workspace-plus-companion combination is the only shape that stages a lock: core.zig points the workspace installer at a temporary file and compares what it generates against the committed one. But the installer returns as soon as every dependency is found up to date — before it builds any lock at all:

if (ws_skipped_count == all_deps_count) {
    style.print("... packages + ... workspace members up to date\n", ...);
    return .{ .exit_code = 0 };     // ← nothing staged
}

So nothing was written, the comparison found no generated lock, and it reported the committed one stale.

Which is why it fires on exactly the runs where nothing is wrong. The first install resolves packages and takes the long path, so it passes; every run after that finds them cached, takes this return, and fails. That is every CI run after the first — on the project shape the companion file exists to support.

The early return now stands only when nobody asked for a staged lock. Falling through costs nothing when they did: every dependency has just been found up to date, so the passes below skip them all and the only work left is building the lock that was requested.

Verified on a workspace with deps.yaml and two npm members:

  • frozen installs pass repeatedly, where before they failed from the second run onward
  • the lockfile is left byte-identical by a frozen run
  • adding an undeclared dependency is still reported out of date — the check has not been turned into a no-op, which was the thing worth being careful about
  • full Zig suite 386/386

This was the last thing blocking stacksjs/stacks#2533 from re-enabling its pantry.lock CI guard.

Sign in to comment on this issue.