Skip to content

fix(install): skip lockfile-level diff checks in recursive workspace comparisons#29052

Open
hassellof wants to merge 4 commits intooven-sh:mainfrom
hassellof:claude/fix-frozen-lockfile-workspace-spurious-diff
Open

fix(install): skip lockfile-level diff checks in recursive workspace comparisons#29052
hassellof wants to merge 4 commits intooven-sh:mainfrom
hassellof:claude/fix-frozen-lockfile-workspace-spurious-diff

Conversation

@hassellof
Copy link
Copy Markdown

What does this PR do?

Fixes bun install --frozen-lockfile (and --production, which implies frozen lockfile) always failing in workspace monorepos, even when the lockfile was just generated by bun install on the same machine.

Root cause: Package.Diff.generate is called recursively — once at the root level to compare the root package, and then for each workspace package. The recursive calls receive the global from_lockfile and to_lockfile, not per-workspace lockfiles. When any workspace package.json has trustedDependencies, the temp lockfile accumulates state across workspace comparisons, producing spurious diffs that mark every subsequent workspace as "updated". This triggers unnecessary dependency re-resolution, which in large monorepos produces a different hoisted dependency tree, causing the frozen-lockfile eql check to fail. The same class of bug affects overrides and patched_dependencies.

Fix: Guard the overrides, trusted_dependencies, and patched_dependencies comparisons with is_root checks, matching the existing guard on catalogs (which was already correctly wrapped in if (is_root)). These lockfile-level concepts are already correctly compared in the root-level call — the recursive workspace calls were redundant and harmful.

Fixes #19088

Reproduction: hassellof/bun-frozen-lockfile-repro (minimal). Also consistently reproduced against a private Phystack monorepo with 4 apps, 6 shared workspace packages, and ~4600 total dependencies.

How did you verify your code works?

Tested against the real-world workspace monorepo described above:

# Before fix:
bun install                    # succeeds
bun install --frozen-lockfile  # FAILS: "lockfile had changes, but lockfile is frozen"
bun install --production       # FAILS: same error

# After fix:
bun install                    # succeeds
bun install --frozen-lockfile  # succeeds
bun install --production       # succeeds

All 63 existing workspace tests pass (0 failures), including the new regression test.

Also verified that the binary lockfile format (bun.lockb) was unaffected — it uses a different comparison path (hasMetaHashChanged) that doesn't have this issue.

Test plan

  • Verified fix against real workspace monorepo reproduction case
  • Verified --frozen-lockfile passes after clean install
  • Verified --production (which implies --frozen-lockfile) passes
  • Verified binary lockfile still works
  • Regression test added (test/cli/install/bun-workspaces.test.ts)
  • All 63 workspace tests pass

🤖 Generated with Claude Code

hassellof and others added 2 commits April 8, 2026 22:32
…comparisons

`Package.Diff.generate` is called recursively to compare individual
workspace packages, but it receives the *global* `from_lockfile` and
`to_lockfile` — not per-workspace lockfiles. The temp lockfile used
for the fresh package.json parse has no trusted_dependencies (and
potentially different overrides/patched_dependencies), so the
lockfile-level comparisons produced spurious diffs for every workspace
package. This caused `--frozen-lockfile` and `--production` (which
implies frozen lockfile) to always fail in workspace monorepos, even
when the lockfile was just generated.

Guard the overrides, trusted_dependencies, and patched_dependencies
comparisons with `is_root` checks, matching the existing guard on
catalogs. These lockfile-level concepts are already correctly compared
in the root-level call.

Fixes oven-sh#19088

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…onorepos

Verifies that `--frozen-lockfile` and `--production` succeed immediately
after `bun install` in a workspace monorepo where packages depend on each
other via `workspace:*`.

Regression test for oven-sh#19088.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 8, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a609d3cf-1931-4016-b749-b1c9fbf20ce1

📥 Commits

Reviewing files that changed from the base of the PR and between fabc928 and 1d7e168.

📒 Files selected for processing (1)
  • test/cli/install/bun-workspaces.test.ts

Walkthrough

Lockfile diff logic now skips several global-scope comparisons when invoked non-root to avoid spurious diffs during recursive comparisons. A regression test for workspace monorepo frozen-lockfile/production installs was added.

Changes

Cohort / File(s) Summary
Lockfile Diff Logic
src/install/lockfile/Package.zig
In Package.Diff.Summary.generate, gate lockfile-scope comparisons so overrides_changed, trusted_dependencies, and patched_dependencies_changed are only evaluated for root invocations (is_root / id_mapping != null), preventing false positives during recursive diffing.
Workspace Frozen-Lockfile Regression Test
test/cli/install/bun-workspaces.test.ts
Add a regression test that sets up a workspace monorepo (workspace:* dependency plus trustedDependencies) and asserts bun install, bun install --frozen-lockfile, and bun install --production run without reporting lockfile changes.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and specifically describes the main change: adding is_root checks to skip lockfile-level diff comparisons in recursive workspace invocations.
Description check ✅ Passed The description is comprehensive and complete, covering root cause analysis, the fix strategy, verification steps, and a detailed test plan matching the template structure.
Linked Issues check ✅ Passed The PR directly addresses #19088 by fixing the frozen-lockfile regression in workspace monorepos. The code changes guard lockfile-level comparisons with is_root checks to prevent spurious diffs from recursive invocations.
Out of Scope Changes check ✅ Passed All changes are strictly scoped to fixing the frozen-lockfile workspace issue: core fix in Package.Diff.Summary.generate and a regression test validating the fix.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/cli/install/bun-workspaces.test.ts`:
- Around line 1990-2020: The tests call spawn(...) and immediately assert
expect(await exited).toBe(0) which hides stderr output on failures; update each
spawn call to also destructure and capture stderr (e.g., { exited, stderr } =
spawn(...)), await the stderr content before asserting the exit code, and add an
assertion or include stderr in the failure message (for the spawn invocations
around bunExe(), packageDir and env) so stderr is visible when the process
fails.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3446d2d5-e751-4740-a873-72f4c8109337

📥 Commits

Reviewing files that changed from the base of the PR and between 1afabdd and b58f0f1.

📒 Files selected for processing (2)
  • src/install/lockfile/Package.zig
  • test/cli/install/bun-workspaces.test.ts

Comment thread test/cli/install/bun-workspaces.test.ts Outdated
Follows the coding guideline to check stderr before exit code for more
actionable failure messages when spawned processes fail.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/cli/install/bun-workspaces.test.ts`:
- Around line 1953-1957: The test case titled "frozen lockfile succeeds in
workspace monorepo with workspace: deps" is a regression test for issue `#19088`
and must be relocated to the canonical regression location; move the test
(including its surrounding describe/context and any fixtures it depends on) out
of test/cli/install/bun-workspaces.test.ts and into a new file named for the
issue (issue 19088), ensure the test file exports/defines the same test name and
preserves any helper imports or setup used by the original (update relative
imports if needed), and remove the original duplicate from
bun-workspaces.test.ts so the test now lives only in
test/regression/issue/19088.test.ts.
- Around line 1958-1985: The test fixture only covers workspace:* dependencies
and misses lockfile-level fields that triggered the regression; update the JSON
written by the write call for the workspace package (e.g., the call that writes
join(packageDir, "packages", "shared", "package.json")) to include a
lockfile-level field such as "trustedDependencies" with at least one entry
(e.g., map a package name to a version or boolean) so the test exercises
recursive diffing of lockfile-level fields like
trustedDependencies/overrides/patchedDependencies and locks the fix; ensure the
added field is valid JSON and placed alongside existing fields like
name/version/dependencies.
- Around line 1998-2000: The test currently asserts
expect(stderr).not.toContain("error") which is too broad and can cause false
failures; update the assertion in the test that reads proc.stderr.text()
(variable stderr) to either remove the broad check entirely and rely on
expect(await proc.exited).toBe(0), or change it to assert absence of the
specific regression message you care about (e.g., the exact error substring
previously observed) so only that exact string is checked rather than any
occurrence of "error".
- Around line 1990-2026: Wrap each Bun.spawn invocation with the resource-safe
pattern using "await using" so spawned processes are disposed correctly: replace
the three raw calls that assign to "proc" (the ones invoking spawn({ cmd:
[bunExe(), 'install' ...] }), spawn({ cmd: [bunExe(), 'install',
'--frozen-lockfile' ...] }), and spawn({ cmd: [bunExe(), 'install',
'--production' ...] })) with "await using proc = spawn(...)" (or equivalent) so
the proc.stderr.text() and proc.exited checks run inside the scope and the
process is cleaned up automatically; ensure you keep references to
"proc.stderr.text()" and "proc.exited" calls inside each using block.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 07099780-0667-4ea3-9f6c-77923779e00c

📥 Commits

Reviewing files that changed from the base of the PR and between b58f0f1 and fabc928.

📒 Files selected for processing (1)
  • test/cli/install/bun-workspaces.test.ts

Comment thread test/cli/install/bun-workspaces.test.ts Outdated
Comment on lines +1953 to +1957
// Regression test for https://github.com/oven-sh/bun/issues/19088
// --frozen-lockfile was failing in workspace monorepos because lockfile-level
// properties (trusted dependencies, overrides, patched dependencies) were
// spuriously compared during recursive per-workspace diff calls.
test("frozen lockfile succeeds in workspace monorepo with workspace: deps", async () => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Place this issue-numbered regression test under test/regression/issue/19088.test.ts.

Since this test is explicitly tied to issue #19088, it should live in the regression issue folder with the real issue number.

As per coding guidelines: “Tests for specific numbered GitHub Issues should be placed in test/regression/issue/${issueNumber}.test.ts with a REAL issue number.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/cli/install/bun-workspaces.test.ts` around lines 1953 - 1957, The test
case titled "frozen lockfile succeeds in workspace monorepo with workspace:
deps" is a regression test for issue `#19088` and must be relocated to the
canonical regression location; move the test (including its surrounding
describe/context and any fixtures it depends on) out of
test/cli/install/bun-workspaces.test.ts and into a new file named for the issue
(issue 19088), ensure the test file exports/defines the same test name and
preserves any helper imports or setup used by the original (update relative
imports if needed), and remove the original duplicate from
bun-workspaces.test.ts so the test now lives only in
test/regression/issue/19088.test.ts.

Comment thread test/cli/install/bun-workspaces.test.ts
Comment thread test/cli/install/bun-workspaces.test.ts Outdated
Comment thread test/cli/install/bun-workspaces.test.ts Outdated
- Add trustedDependencies to workspace fixture to exercise the actual
  root-cause path (lockfile-level field contamination across recursive
  workspace diff calls)
- Use `await using` for spawn resource cleanup
- Drop broad "error" stderr check; use specific "lockfile had changes"
  assertion on all three spawn calls
- Use block-scoped const instead of var reassignment

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

(Regression) bun install --frozen-lockfile keeps reporting issues

1 participant