Skip to content

Conversation

@robobun
Copy link
Collaborator

@robobun robobun commented Jan 15, 2026

Summary

  • Fixes @types/* packages in .bun/node_modules/ being resolved based on alphabetical workspace package name ordering
  • Now hoists the highest semver version instead of the first-seen version
  • Only affects npm packages (git, tarball, etc. resolutions keep first-seen behavior)

Test plan

  • Added regression test in test/regression/issue/26140.test.ts
  • Test passes with debug build (bun bd test test/regression/issue/26140.test.ts)
  • Test fails with system Bun, proving the fix works

Fixes #26140

🤖 Generated with Claude Code

Previously, the hoisted version in `.bun/node_modules/` was determined by
alphabetical workspace ordering - whichever workspace came first alphabetically
would have its version of a shared dependency hoisted. This caused issues in
monorepos where different workspaces depend on different versions of the same
package (e.g., @types/react@18 vs @types/react@19).

Now, when multiple versions of a package are encountered, the highest version
(by semver) is hoisted instead of the first-seen version. This ensures
consistent behavior regardless of workspace naming.

Fixes #26140

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@robobun
Copy link
Collaborator Author

robobun commented Jan 15, 2026

Updated 1:19 PM PT - Jan 16th, 2026

@autofix-ci[bot], your commit 94691cb has 2 failures in Build #35036 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 26141

That installs a local version of the PR into your bun-26141 executable, so you can run:

bun-26141 --bun

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 15, 2026

Walkthrough

Implements version-aware hoisting by introducing HoistInfo and changing hoist storage to track entry and package IDs; updates hoisting logic to prefer higher npm semantic versions when deciding which entry to hoist. Adds regression tests verifying highest-version hoisting in workspace monorepos. Minor import reorder in Request.zig.

Changes

Cohort / File(s) Summary
Version-aware hoisting implementation
src/install/isolated_install.zig
Add HoistInfo struct; change hidden_hoisted to store HoistInfo; compute new_entry_id earlier; compute should_hoist conditionally; hoist first-seen names with HoistInfo; when an existing hoist exists and both resolutions are npm, compare semantic versions and replace the hoisted entry if the new version is greater; preserve first-seen behavior for non-npm resolutions.
Hoisting regression tests
test/regression/issue/26140.test.ts
Add two workspace-focused tests that create monorepos with conflicting is-number versions and assert .bun/node_modules/is-number is hoisted to the highest version regardless of workspace name ordering.
Import reorder (no behavior change)
src/bun.js/webcore/Request.zig
Move FetchRedirect.zig import later in the file; no functional or API changes.

Suggested reviewers

  • dylan-conway
🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Out of Scope Changes check ❓ Inconclusive One minor out-of-scope change detected: reordering imports in Request.zig appears unrelated to the hoisting fix for isolated installs. Clarify whether the import reordering in src/bun.js/webcore/Request.zig is intentional or an accidental inclusion in this PR.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed Title accurately summarizes the main change: fixing hoisting behavior to select the highest version instead of alphabetical ordering.
Description check ✅ Passed Description covers what the PR does and test plan, but lacks a 'How did you verify your code works?' section matching the template.
Linked Issues check ✅ Passed PR addresses the core requirement from issue #26140: hoisting the highest semver version instead of relying on alphabetical package name ordering for npm packages.

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


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

Copy link
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: 2

🤖 Fix all issues with AI agents
In `@test/regression/issue/26140.test.ts`:
- Around line 122-126: Remove the redundant stderr content assertions and only
assert the process exit code: delete the two lines with
expect(stderr).not.toContain("panic:") and
expect(stderr).not.toContain("error:") in the test that awaits
Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); keep the
await of proc outputs if needed and retain the expect(exitCode).toBe(0)
assertion.
- Around line 57-61: Remove the unused stdout capture and the fragile stderr
string assertions: instead of awaiting Promise.all([proc.stdout.text(),
proc.stderr.text(), proc.exited]) and asserting on stdout/stderr contents, await
only proc.exited to get exitCode (e.g., const exitCode = await proc.exited) and
keep only expect(exitCode).toBe(0); remove the variables stdout and stderr and
the lines expect(stderr).not.toContain("panic:") and
expect(stderr).not.toContain("error:").
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 97feb66 and 15c990b.

📒 Files selected for processing (2)
  • src/install/isolated_install.zig
  • test/regression/issue/26140.test.ts
🧰 Additional context used
📓 Path-based instructions (3)
test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}: Use bun bd test <...test file> to run tests with compiled code changes. Do not use bun test as it will not include your changes.
Use bun:test for files ending in *.test.{ts,js,jsx,tsx,mjs,cjs}. For test files without .test extension in test/js/node/test/{parallel,sequential}/*.js, use bun bd <file> instead of bun bd test <file> since they expect exit code 0.
Do not set a timeout on tests. Bun already has timeouts built-in.

Files:

  • test/regression/issue/26140.test.ts
test/**/*.test.ts

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.test.ts: Use Bun's Jest-compatible test runner with proper test fixtures and imports from harness
Always use port: 0 when binding to ports in tests - never hardcode ports or use custom random port functions
Use normalizeBunSnapshot to normalize snapshot output in tests
Never write tests that check for 'panic', 'uncaught exception', or similar strings in test output as these will never fail in CI
Use tempDir from harness to create temporary directories in tests - do not use tmpdirSync or fs.mkdtempSync
In spawned process tests, use expect(stdout).toBe(...) BEFORE expect(exitCode).toBe(0) for more useful error messages
Do not use setTimeout in tests - await the condition to be met instead, as you are testing the CONDITION not TIME PASSING

Files:

  • test/regression/issue/26140.test.ts
src/**/*.zig

📄 CodeRabbit inference engine (src/CLAUDE.md)

src/**/*.zig: Use the # prefix for private fields in Zig structs, e.g., struct { #foo: u32 };
Use Decl literals in Zig, e.g., const decl: Decl = .{ .binding = 0, .value = 0 };
Place @import statements at the bottom of the file in Zig (auto formatter will handle positioning)
Never use @import() inline inside functions in Zig; always place imports at the bottom of the file or containing struct

Files:

  • src/install/isolated_install.zig
🧠 Learnings (13)
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8.test.ts : Add corresponding test cases to test/v8/v8.test.ts using checkSameOutput() function to compare Node.js and Bun output

Applied to files:

  • test/regression/issue/26140.test.ts
📚 Learning: 2025-10-19T02:44:46.354Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: packages/bun-otel/context-propagation.test.ts:1-1
Timestamp: 2025-10-19T02:44:46.354Z
Learning: In the Bun repository, standalone packages under packages/ (e.g., bun-vscode, bun-inspector-protocol, bun-plugin-yaml, bun-plugin-svelte, bun-debug-adapter-protocol, bun-otel) co-locate their tests with package source code using *.test.ts files. This follows standard npm/monorepo patterns. The test/ directory hierarchy (test/js/bun/, test/cli/, test/js/node/) is reserved for testing Bun's core runtime APIs and built-in functionality, not standalone packages.

Applied to files:

  • test/regression/issue/26140.test.ts
📚 Learning: 2026-01-15T03:22:50.711Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T03:22:50.711Z
Learning: For GitHub Issue-specific tests, place the test file in `test/regression/issue/${issueNumber}.test.ts` with a REAL issue number

Applied to files:

  • test/regression/issue/26140.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Organize regression tests for specific issues in `/test/regression/issue/${issueNumber}.test.ts`. Do not place regression tests in the regression directory if there is no associated issue number.

Applied to files:

  • test/regression/issue/26140.test.ts
📚 Learning: 2026-01-15T03:22:50.711Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T03:22:50.711Z
Learning: Applies to test/**/*.test.ts : Use Bun's Jest-compatible test runner with proper test fixtures and imports from `harness`

Applied to files:

  • test/regression/issue/26140.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `bun bd test <...test file>` to run tests with compiled code changes. Do not use `bun test` as it will not include your changes.

Applied to files:

  • test/regression/issue/26140.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `bun:test` for files ending in `*.test.{ts,js,jsx,tsx,mjs,cjs}`. For test files without .test extension in test/js/node/test/{parallel,sequential}/*.js, use `bun bd <file>` instead of `bun bd test <file>` since they expect exit code 0.

Applied to files:

  • test/regression/issue/26140.test.ts
📚 Learning: 2026-01-15T03:22:50.711Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T03:22:50.711Z
Learning: Applies to test/**/*.test.ts : Use `normalizeBunSnapshot` to normalize snapshot output in tests

Applied to files:

  • test/regression/issue/26140.test.ts
📚 Learning: 2025-11-20T19:51:32.288Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 24880
File: packages/bun-vscode/package.json:382-385
Timestamp: 2025-11-20T19:51:32.288Z
Learning: In the Bun repository, dependencies may be explicitly added to package.json files (even when not directly imported in code) to force version upgrades on transitive dependencies, particularly as part of Aikido security scanner remediation to ensure vulnerable transitive dependencies resolve to patched versions.

Applied to files:

  • test/regression/issue/26140.test.ts
📚 Learning: 2026-01-14T21:08:10.438Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/js/node/test/parallel/CLAUDE.md:0-0
Timestamp: 2026-01-14T21:08:10.438Z
Learning: These are Node.js compatibility tests not written by Bun and cannot be modified

Applied to files:

  • test/regression/issue/26140.test.ts
📚 Learning: 2025-10-26T01:32:04.844Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24082
File: test/cli/test/coverage.test.ts:60-112
Timestamp: 2025-10-26T01:32:04.844Z
Learning: In the Bun repository test files (test/cli/test/*.test.ts), when spawning Bun CLI commands with Bun.spawnSync for testing, prefer using stdio: ["inherit", "inherit", "inherit"] to inherit stdio streams rather than piping them.

Applied to files:

  • test/regression/issue/26140.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: When spawning Bun processes in tests, use `bunExe` and `bunEnv` from `harness` to ensure the same build of Bun is used and debug logging is silenced.

Applied to files:

  • test/regression/issue/26140.test.ts
📚 Learning: 2026-01-05T16:32:07.551Z
Learnt from: alii
Repo: oven-sh/bun PR: 25474
File: src/bun.js/event_loop/Sigusr1Handler.zig:0-0
Timestamp: 2026-01-05T16:32:07.551Z
Learning: In Zig codebases (e.g., Bun), treat std.posix.sigaction as returning void and do not perform runtime error handling for its failure. The Zig standard library views sigaction failures as programmer errors (unreachable) because they only occur with invalid signals like SIGKILL/SIGSTOP. Apply this pattern across Zig files that call sigaction (e.g., crash_handler.zig, main.zig, filter_run.zig, process.zig) and ensure failures are not handled as recoverable errors; prefer reaching an explicit unreachable/compile-time assumption when such failures are detected.

Applied to files:

  • src/install/isolated_install.zig
🧬 Code graph analysis (1)
test/regression/issue/26140.test.ts (1)
test/harness.ts (1)
  • tempDir (277-284)
🔇 Additional comments (4)
test/regression/issue/26140.test.ts (1)

15-77: Well-structured regression test with good coverage.

The test correctly validates the fix by:

  1. Setting up a monorepo with workspaces having different is-number versions
  2. Verifying the higher version (7.0.0) is hoisted regardless of alphabetical order
  3. Confirming both versions are installed in their respective locations

The use of using for automatic temp directory cleanup and the clear test documentation are appreciated. Based on learnings, this test is correctly placed in test/regression/issue/${issueNumber}.test.ts.

src/install/isolated_install.zig (3)

423-428: Good introduction of HoistInfo struct for tracking hoisted entries.

The struct appropriately captures both the entry ID and package ID needed for version comparison and potential un-hoisting. The change from void to HoistInfo in the hash map is a clean way to track the necessary metadata.


541-576: Correct implementation of highest-version hoisting logic.

The logic properly:

  1. Hoists first-seen packages immediately
  2. Compares npm versions when a package name is encountered again
  3. Un-hoists the previous entry and hoists the new one when the new version is higher
  4. Preserves first-seen behavior for non-npm resolution types (git, tarball, etc.)

The use of new_version.order(existing_version, string_buf, string_buf) == .gt correctly determines if the new version is greater.


523-524: Good placement of new_entry_id calculation.

Moving the new_entry_id calculation before the hoisting logic ensures it's available for storing in HoistInfo when the entry is hoisted. This is necessary since the store hasn't been appended to yet at this point.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Remove unused stdout capture and fragile stderr string assertions.
Only check exit code for install success.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@dylan-conway
Copy link
Member

This is the cause of the issue. Fix this behavior difference

Concise Summary                                                                                                                                                                                                             
                                                                                                                                                                                                                              
  pnpm: Processes workspace packages in filesystem path order (packages/aaa before packages/bbb). First workspace to declare a dependency name wins the hoisted slot.                                                         
                                                                                                                                                                                                                              
  Bun bug: Sorts by package.json name instead of filesystem path.                                                                                                                                                             
                                                                                                                                                                                                                              
  Fix: Sort workspace packages by their importer/filesystem path to match pnpm. 

@alii
Copy link
Member

alii commented Jan 16, 2026

@claude Concise Summary

pnpm: Processes workspace packages in filesystem path order (packages/aaa before packages/bbb). First workspace to declare a dependency name wins the hoisted slot.

Bun bug: Sorts by package.json name instead of filesystem path.

Fix: Sort workspace packages by their importer/filesystem path to match pnpm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@types/* packages in .bun/node_modules/ resolved based on alphabetical workspace package name ordering

4 participants