ReviewOS

pantry-pm/pantry

Workspace lock regeneration drops other-platform native dependency pins

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

Pantry 0.11.55 produces different workspace pantry.lock contents on Linux and macOS because host-specific companion dependencies are lost during workspace lock regeneration. This leaves an otherwise unchanged checkout dirty and prevents a stable cross-platform frozen installation.

Observed with the official 0.11.55 binaries reporting 4fa8b3a and Bun 1.4.1, on stacksjs/stacks at 916b3d2. The Darwin binary SHA-256 was verified against release-manifest.json.

Reproduction:

  1. Install the Stacks workspace on Linux using Pantry 0.11.55. The generated lock includes nine Linux native records listed below.
  2. Start a fresh copy of the same revision on macOS arm64, replace pantry.lock with that generated Linux lock, and run the same Pantry version with Bun 1.4.1 selected in PATH.
  3. The macOS install drops exactly those nine records. Every common package record and workspace entry stays identical; bun.lock remains byte-identical.
  4. A repeated frozen install accepts that Darwin-generated lock, while the original committed lock fails as out of date. This same-host stability does not provide cross-platform stability.

Removed Linux records: gnome.org/libxml2@2.13.9, gnu.org/binutils@2.46.0, gnu.org/gcc/libstdcxx@14.3.0, gnu.org/gettext@0.21.1, gnu.org/gmp@6.3.0, gnu.org/libiconv@1.19.0, gnu.org/mpc@1.3.1, gnu.org/mpfr@4.2.2, tukaani.org/xz@5.8.2.

Pinned-source investigation: workspace.zig starts a new empty lock. Companion merging then records successful current install results, while pipeline.zig filters OS-prefixed specs against the current host. Foreign-platform pins removed by regeneration cannot be restored by that host-only result set.

Expected: separate the cross-platform dependency graph recorded in the lock from the host-specific installation plan. Add Linux/Darwin regeneration and frozen-install regression coverage while still allowing obsolete dependency pins to be removed. Copying one platform lock over the other, ignoring the file, or resetting it after installation does not fix the problem.

Evidence and downstream tracking: https://github.com/stacksjs/stacks/issues/2533 and https://github.com/stacksjs/stacks/actions/runs/34289135740/artifacts/10080721269 (Linux source patch, 30-day retention). Full local logs, release manifest/hash, generated Linux/Darwin locks, and semantic comparisons are retained in the downstream investigation. The local installer also reports a separate post-database migration error despite exiting zero, so these observations establish lock behavior, not a successful full application setup.

8 days ago

Additional controlled check: using the already-normalized Linux lock (matching workspace metadata, without the 80 stale records), Pantry 0.11.55 on Darwin with Bun 1.4.1 rejects install --frozen-lockfile as out of date and leaves the input lock byte-identical. The Darwin-regenerated lock passes the lock check. This isolates cross-platform failure from the older workspace metadata drift. The separate application migration collision is now tracked in stacksjs/stacks#2534.

8 days ago

Fixed in 67d16b0, released as 0.11.56.

Your diagnosis was right down to both line references. The two halves are exactly as you named them: parsePantryDepSpec answers null for a spec guarded behind another OS — correct for installing, since there is nothing on this host to fetch — and installWorkspaceCommandWithOptions builds its lock from a fresh empty Lockfile out of what this host actually resolved. Correct for installing, wrong for recording: the lockfile describes the dependency graph, and that graph contains the Linux-only libstdcxx whether or not a Mac can install it.

What changed

parseForeignOsDepSpec is the counterpart to the existing parser. It answers only for a spec guarded for an OS this host is not, and null both for a spec meant for this host and for a malformed one — a caller asking "which pins does this host legitimately not know about" must not be handed junk. The two partition every spec between them, which is what the first of three new unit tests asserts.

Regeneration then restores those pins from the previous lock, which is the "separate the recorded graph from the host-specific install plan" you asked for, at the smallest seam that achieves it.

Two wrong turns, recorded because both look right

  • The catalog lookup has to read the existing lock, not the regenerated one. The regenerated lock holds no system packages at that point — the companion step merges them afterwards — so looking there finds nothing and the entire pass is a silent no-op. My first version built, ran, and did nothing at all.
  • Carrying every absent pantry entry forward is simpler and wrong. It also resurrects genuinely obsolete pins, breaking the property this issue explicitly requires. Caught by watching an unrelated stale record come back from the dead.

Verification

A workspace whose committed lock holds a Linux-only pin (gnu.org/gcc/libstdcxx@15.2.0, reached via ccache.dev's linux:gnu.org/gcc/libstdcxx@15) plus a deliberately obsolete one:

linux-only pinobsolete pin
0.11.55droppedpruned
0.11.56keptpruned

Repeat installs produce a byte-identical lock. Full Zig suite 386/386.

A separate regression on main, not from this change

While verifying, --frozen-lockfile on a workspace with a companion deps file rejects a lock the tool has just written:

[dbg] only in committed: is-odd@3.0.1
[dbg] only in committed: is-number@7.0.0

The generated lock is missing the npm entries the committed one has. Unmodified main does this too, and 0.11.55 does not — I checked by stashing my change, rebuilding, and re-running, which is the only reason I can say it is not mine. It affects any workspace + companion-deps project using --frozen-lockfile in CI, so it is worth its own issue; I have not touched it here.

Sign in to comment on this issue.