Skip to content

install: make bun update re-resolve root peerDependencies - #36688

Closed
robobun wants to merge 4 commits into
mainfrom
farm/3a6a364e/update-peer-deps
Closed

robobun wants to merge 4 commits into
mainfrom
farm/3a6a364e/update-peer-deps

Conversation

@robobun

@robobun robobun commented Aug 1, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #13509.

Repro

mkdir app && cd app
echo '{"name":"repro","peerDependencies":{"typescript":"5.5.2"}}' > package.json
bun install
echo '{"name":"repro","peerDependencies":{"typescript":"^5.5.2"}}' > package.json
bun install
bun update                      # "no changes"
bun update typescript           # "installed typescript@5.5.2"

bun outdated sees the newer version but bun update never moves it.

Cause

Peer dependencies are resolved in a deferred second pass with install_peer = true. get_or_put_resolved_package has a peer-reuse path that scans package_index for any existing entry whose resolution satisfies the requested range and short-circuits to it. That is correct for transitive peers (they piggyback on whatever sibling already provides the package) but the stale lockfile entry for typescript@5.5.2 is still in the index, satisfies ^5.5.2, and wins.

The should_update gate that forces a fresh resolve for update targets already existed in get_or_put_resolved_package_with_find_result, but that runs after the peer-reuse short-circuit, so it was never consulted for peers.

Fix

Extract the existing check into should_update_dependency() and use it to skip the peer-reuse path when the dependency is a direct update target (root-level, and either bun update was run without args or the name matches an explicit request). Transitive peers are unaffected because is_root_dependency is false for them.

Verification

New tests in the update describe block of bun-install-registry.test.ts seed the lockfile with a-dep@1.0.1, widen to ^1.0.1, then run each of bun update, bun update a-dep, and bun update --latest. All three now land on 1.0.10 and rewrite peerDependencies in package.json. The first two fail on main (stay at 1.0.1).

Existing update and peer test groups in bun-install-registry.test.ts, plus bun-lock.test.ts -t peer and catalogs.test.ts -t update, continue to pass.


no test proof · iteration 0 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/cli/install/bun-install-registry.test.ts

When a package is listed in peerDependencies and a lockfile already has
an older version that still satisfies the range, `bun update` (and
`bun update <pkg>`) would reuse that stale entry instead of picking the
newest matching version.

Peer dependencies are resolved in a second pass (install_peer=true). The
peer-reuse path in get_or_put_resolved_package scans package_index for
any existing entry whose resolution satisfies the range and short
circuits to it, which is correct for transitive peers but skipped the
should_update check that direct dependencies already get via
get_or_put_resolved_package_with_find_result.

Extract should_update_dependency() and use it to bypass the reuse path
for direct peer dependencies that are update targets.

Fixes #13509
@coderabbitai

coderabbitai Bot commented Aug 1, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 17 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a1de909c-ae5e-4245-a43b-7fc84b2e7de6

📥 Commits

Reviewing files that changed from the base of the PR and between 7075c37 and e3a16b1.

📒 Files selected for processing (1)
  • src/install/PackageManager/PackageManagerEnqueue.rs

Walkthrough

bun update now centralizes dependency selection, including catalog and root dependencies. Selected dependencies bypass deferred peer reuse. Registry tests cover unnamed, named, and --latest updates with stale peer lockfile entries.

Changes

Peer dependency update resolution

Layer / File(s) Summary
Centralized update target detection
src/install/PackageManager/PackageManagerEnqueue.rs
Added should_update_dependency and used it when resolving packages for updates.
Peer reuse and regression coverage
src/install/PackageManager/PackageManagerEnqueue.rs, test/cli/install/bun-install-registry.test.ts
Selected dependencies no longer reuse deferred peer packages. Tests cover three update command forms and verify resolution to 1.0.10 with ^1.0.10 saved in package.json.

Possibly related PRs

  • oven-sh/bun#36360: Both change update-target detection and resolved-package reuse.
  • oven-sh/bun#36379: Both update re-resolution logic for dependencies and catalog references.
  • oven-sh/bun#36646: Both change peer-dependency resolution in PackageManagerEnqueue.rs.

Suggested reviewers: jarred-sumner

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: re-resolving root peer dependencies during bun update.
Description check ✅ Passed The description explains the cause, fix, reproduction, and verification, although it does not use the template headings exactly.
Linked Issues check ✅ Passed The implementation addresses issue #13509 by updating stale root peer dependencies for supported bun update commands.
Out of Scope Changes check ✅ Passed The code and tests are limited to fixing root peer dependency updates and verifying the related bun update behavior.

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

@github-actions github-actions Bot added the claude label Aug 1, 2026
@robobun

robobun commented Aug 1, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status: closed by the stale sweep, not merged. The branch is out of date and conflicts with main.

Original repro, against the base of this branch:

bun bd test test/cli/install/bun-install-registry.test.ts -t 'peer dependency with stale lockfile entry'

@robobun

robobun commented Aug 1, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 4:05 AM PT - Aug 1st, 2026

❌ @autofix-ci[bot], your commit e3a16b1 has 2 failures in Build #87056 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 36688

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

bun-36688 --bun

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/cli/install/bun-install-registry.test.ts`:
- Around line 4970-5011: Extend the existing stale-lockfile test matrix around
the peer dependency update case to cover catalog-backed peerDependencies, while
preserving the current no-argument, named-package, and --latest variants.
Configure the test package and catalog so a-dep resolves through a catalog
entry, then verify bun update refreshes the stale lockfile target and updates
the resulting manifest and installed version consistently with the existing
cases.
🪄 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: d1655cc9-e115-474f-85e9-5b28c939c8e2

📥 Commits

Reviewing files that changed from the base of the PR and between a7838c5 and 7075c37.

📒 Files selected for processing (2)
  • src/install/PackageManager/PackageManagerEnqueue.rs
  • test/cli/install/bun-install-registry.test.ts

@claude claude Bot left a comment

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.

No bugs found. The extraction of should_update_dependency() is behavior-preserving and the new gate is scoped to bun update only (to_update early-returns false otherwise), but this touches peer resolution ordering in the package manager — worth a human look to confirm skipping the peer-reuse short-circuit for root peers has no unintended effect on multi-version peer scenarios.

Checked: the extracted function is semantically identical to the removed inline block (early-return on !to_update ≡ the old && prefix); the updated SAFETY comment now correctly matches what is_root_dependency actually reads (root_package_id/workspace_name_hash); transitive peers are unaffected since is_root_dependency is false for them; bun install is unaffected via the to_update guard. The comment-cop warnings are firing on a moved pre-existing SAFETY comment, not new justification.

Extended reasoning...

Overview

Two files: src/install/PackageManager/PackageManagerEnqueue.rs extracts the existing inline should_update computation from get_or_put_resolved_package_with_find_result into a standalone should_update_dependency() function, then calls it at one new site — the top of get_or_put_resolved_package — to skip the peer-reuse short-circuit when the peer is a direct bun update target. test/cli/install/bun-install-registry.test.ts adds a 3-variant test loop (no args, named, --latest) that seeds a stale lockfile entry and asserts it re-resolves.

Security risks

None. No untrusted input parsing, no new unsafe — the raw-pointer split for the is_root_dependency borrowck workaround is moved verbatim from the original site, and the SAFETY invariant (disjoint fields from lockfile) still holds per lockfile.rs:859-863.

Level of scrutiny

Medium. The diff itself is mechanically simple (extract + one guard), and the guard is dead code outside bun update because of the if !this.to_update { return false } early exit. But it changes which branch peer dependencies take during resolution, and the package manager's resolution ordering is subtle enough (see the long determinism comment immediately below the extracted call site) that a maintainer who knows the peer-hoisting invariants should confirm that bypassing the package_index reuse for root peers can't produce a different result than the phase-2 descending scan would for a root peer that also has transitive providers.

Other factors

  • Tests follow existing patterns in the update describe block (runBunInstall/runBunUpdate, exact version + package.json assertions), and robobun confirmed fail-before with USE_SYSTEM_BUN=1.
  • The two comment-cop bot warnings target the doc comment and the moved borrowck/SAFETY comment — both pre-existing content, not new workaround justification. Not a blocker.
  • CodeRabbit's suggestion to add catalog-backed-peer coverage is a reasonable nit but not a correctness concern; the Tag::Catalog arm was already in the original predicate.

@claude claude Bot left a comment

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.

I didn't find any issues — the extraction of should_update_dependency is byte-identical to the previous inline predicate, and the new gate on the peer-reuse block is narrowly scoped (fires only when to_update and the dep is a root/catalog update target, so bun install and transitive peers are untouched). Deferring to a human because this sits in the package manager's resolution path.

What was reviewed:

  • Confirmed the extracted predicate matches the original inline logic exactly, including the unsafe aliasing pattern and short-circuit ordering.
  • Traced the fallthrough: skipping the peer-reuse block lands in the same npm-manifest → _with_find_result path that non-peer root deps already use under bun update, so no new resolution codepath is introduced.
  • Checked is_root_dependency only touches root_package_id/workspace_name_hash (disjoint from lockfile), matching the SAFETY comment.
  • Tests cover all three update forms (no args, named, --latest), assert both node_modules state and package.json rewrite, and use the hermetic registry harness.
Extended reasoning...

Overview

Two files changed. In PackageManagerEnqueue.rs, the pre-existing inline should_update predicate inside get_or_put_resolved_package_with_find_result is extracted into should_update_dependency() and additionally used to gate the peer-reuse short-circuit at the top of get_or_put_resolved_package. In bun-install-registry.test.ts, three parametrized tests are added inside the existing describe("update") block covering bun update, bun update a-dep, and bun update --latest against a lockfile seeded with a stale peer resolution.

Security risks

None. This is dependency-resolution control flow inside bun update; no parsing of untrusted input, no auth/crypto, no filesystem/network surface changes.

Level of scrutiny

Medium-high. The diff itself is small and mechanical — a pure extraction plus one added && !should_update_dependency(...) conjunct — but it lives in the package manager's resolution path, where a subtle regression could affect every bun update. That said, blast radius is tightly bounded: the new guard early-returns false unless this.to_update, so bun install is entirely unaffected, and it further requires the dependency to be root-level (or catalog) and match the update request set, so transitive peer reuse is unchanged. When the guard fires, control falls through to the same manifest lookup → _with_find_result path that non-peer root dependencies already take under bun update, so no new resolution logic is introduced — peers just stop short-circuiting past it.

Other factors

The extraction is verifiably behavior-preserving: same to_update gate, same Catalog-or-root check, same update_requests.is_empty() || updating_packages.contains(name) clause, same unsafe split-borrow pattern. The updated SAFETY comment (root_package_id/workspace_name_hash) matches what is_root_dependency actually reads at src/install/lockfile.rs:854. All prior bot feedback (comment-cop verbosity, CodeRabbit's catalog-peer coverage suggestion) has been addressed and resolved. The PR description states existing update/peer groups plus bun-lock.test.ts -t peer and catalogs.test.ts -t update continue to pass, and fail-before was verified with USE_SYSTEM_BUN=1. Given the location in resolution logic, a maintainer glance is still warranted.

@github-actions

Copy link
Copy Markdown
Contributor

Closing this PR because it has been inactive for more than 90 days.

@github-actions github-actions Bot closed this Oct 31, 2026
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.

bun outdated doesn't update peer deps

2 participants