Skip to content

install: skip workspace members whose package.json has no name - #38781

Open
robobun wants to merge 4 commits into
mainfrom
farm/9db45ae9/skip-nameless-workspace-members
Open

install: skip workspace members whose package.json has no name#38781
robobun wants to merge 4 commits into
mainfrom
farm/9db45ae9/skip-nameless-workspace-members

Conversation

@robobun

@robobun robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

Problem

  • bun install fails with error: Missing "name" from package.json in pkgs/fixture/package.json as soon as one directory matched by workspaces (glob or listed path) has a package.json without a name, and installs nothing. pnpm 11 and npm 11 install the same tree. Fixtures like {"type":"module"} under a workspaces glob are common: vite v8.2.1 has 23 of them under packages/**/__tests__/** and playground/**, none declaring dependencies. Ignore package.json files without "name" property #6317 reports the same failure for nameless package.json files used as folder metadata under packages/features/*.
  • pnpm-lock.yaml migration fails on such an importer (warn: pnpm-lock.yaml migration failed due to missing workspace name., then InvalidLockfile), and package-lock.json migration fails with InstallFailed (arborist fixture workspaces-need-update).
  • bun install run inside a named member whose sibling is nameless installed the member as a standalone project: the walk that looks for the workspace root (src/install/PackageManager.rs) scans the workspaces with the same code and gives up on the error.
  • Cause: process_workspace_name in src/install/lockfile/Package/WorkspaceMap.rs returned Error::MissingPackageName, which both the listed-path and the glob branch of process_names_array logged as an error; migrate_pnpm_lockfile in src/install/pnpm.rs returned WorkspaceNameMissing for the importer. The scan already skipped "name": "" silently.

Fix

  • process_workspace_name returns Scanned::Nameless for a missing, non-string or empty name and both branches skip the directory (the old empty-name check is folded into this path). The pnpm migrator applies the same rule and skips the importer; the later passes iterate lockfile.workspace_paths, so they never see it. Error::MissingPackageName, WorkspaceNameMissing and their message arms are removed.
  • Skipping rather than deriving a name (npm uses the directory name; install: name folder, tarball and git packages without a package.json name after their source #38681 derives names for folder/tarball/git packages): a workspace's name becomes a root dependency and a node_modules/<name> link, so derived names would link fixtures into node_modules under whatever the directory is called and collide with each other (five of vite's fixtures are in a directory named nested). pnpm does not link them either.
  • When a skipped package.json declares dependencies, bun does not install them, unlike pnpm/npm. The scan records the directory and the root Package::parse warns once: warn: Skipping workspace "packages/fixture": its package.json has no "name", so its dependencies will not be installed. The warning is emitted from the root parse instead of the scan because the migrations, --filter and the root-finding walk also scan during the same command; emitting it in the scan printed it twice during a package-lock.json migration. Fixtures without dependencies (vite's) print nothing.
  • Verified with the debug build; every new test fails on the released binary (the Missing "name" errors, the migration warning, the member installing standalone, and no warning for "name": ""):
    • test/cli/install/bun-workspaces.test.ts, "workspace member package.json without a name": glob, listed path and overlapping patterns that match the fixture three times, each with and without dependencies (the fixture declares a dependency that does not exist, so a successful install proves it was never resolved; the warning is asserted to appear exactly once), empty name, and bun install from a member directory finding the root and warning exactly once.
    • test/cli/install/migration/pnpm-lock-v9.test.ts, "importer whose package.json has no name": workspaces defined only in pnpm-workspace.yaml with a __tests__ glob, as in vite; the migration writes root + packages/a only, and the following bun install --frozen-lockfile passes without rewriting bun.lock, i.e. the scan agrees with the migration. The second case checks the importer's recorded dependency is not migrated and that the install warns.
    • test/cli/install/migration/migrate.test.ts: the arborist workspaces-need-update snapshot now records a successful migration (fixture notes updated).
    • Full runs of bun-workspaces, bad-workspace, frozen-lockfile-missing-workspace, frozen-lockfile-pruned, catalogs, bun-add-catalog, overrides, bun-add-filter, pnpm-lock-v9, pnpm-migration, yarn-lock-migration and migrate.
  • On vite v8.2.1 itself, bun pm migrate now gets past the 23 importers and stops at vite's relative link:./fixtures/... dependencies (bun install: support non-existent workspaces and relative link dependencies #23026), which bun install without a lockfile rejects too, so that repo additionally needs bun install: support non-existent workspaces and relative link dependencies #23026.
  • docs/pm/workspaces.mdx documents the behavior.

Background

  • The root Package::parse calls WorkspaceMap::process_names_array, which resolves every workspaces entry (plain paths directly, globs through a directory walk) to a package.json and keys the map by the member's relative directory. Each entry then becomes a workspace: dependency of the root named after the member, is linked at node_modules/<name>, and is what workspace: specifiers elsewhere resolve to through lockfile.workspace_paths, a map keyed by name hash. A member without a name therefore has nothing to be stored under.
  • The same scan also runs during package-lock.json and yarn.lock migration, --filter target selection, and the startup walk that checks whether the cwd belongs to a workspace further up, each into a different log (the startup walk discards its log).
  • pnpm-lock.yaml lists every directory pnpm-workspace.yaml matched as an importer. bun's migrator registers each importer's name in workspace_paths and finally copies pnpm-workspace.yaml's packages into the root package.json workspaces, so the root parse that follows scans the same globs and must reach the same conclusion about these directories.

Fixes #6317


[review] gate passed · iteration 0 · 11 files touched

fails on main (without fix)
ASAN without fix: 10 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/cli/install/bun-workspaces.test.ts "test/cli/install/migration/pnpm-lock-v9.test.ts"
bun test v1.4.0 (d3b85cbd0)

test/cli/install/bun-workspaces.test.ts:
(pass) dependency on workspace without version in package.json [2875.28ms]
1502 |   expect(stdout).toBeDefined();
1503 |   expect(stderr).toBeDefined();
1504 |   const [err, out, exitCode] = await Promise.all([stderr.text(), stdout.text(), exited]);
1505 |   expect(err).not.toContain("panic:");
1506 |   if (!options?.allowErrors) {
1507 |     expect(err).not.toContain("error:");
                           ^
error: expect(received).not.toContain(expected)

Expected to not contain: "error:"
Received: "error: Missing \"name\" from package.json in packages/fixture/package.json\n    at /tmp/verdaccio-test-_SW2QMz/package.json\n"

      at runBunInstall (/workspace/bun/test/harness.ts:1507:21)
      at async <anonymous> (/workspace/bun/test/cli/install/bun-workspaces.test.ts:284:13)
1502 |   expect(stdout).toBeDefined();
1503 |   expect(stderr).toBeDefined();
1504 |   const [err, out,
... (truncated)

release without fix: 82 FAILED
bun test v1.4.0-canary.1 (b7a043103)

test/cli/install/bun-workspaces.test.ts:
(pass) dependency on workspace without version in package.json [112.99ms]
1502 |   expect(stdout).toBeDefined();
1503 |   expect(stderr).toBeDefined();
1504 |   const [err, out, exitCode] = await Promise.all([stderr.text(), stdout.text(), exited]);
1505 |   expect(err).not.toContain("panic:");
1506 |   if (!options?.allowErrors) {
1507 |     expect(err).not.toContain("error:");
                           ^
error: expect(received).not.toContain(expected)

Expected to not contain: "error:"
Received: "error: Missing \"name\" from package.json in packages/fixture/package.json\n    at /tmp/verdaccio-test-_IRq4jV/package.json\n"

      at runBunInstall (/workspace/bun/test/harness.ts:1507:21)
      at async <anonymous> (/workspace/bun/test/cli/install/bun-workspaces.test.ts:284:13)
1502 |   expect(stdout).toBeDefined();
1503 |   expect(stderr).toBeDefined();
1504 |   const [err, out, exitCode] = await Promise.all([stderr.text(), stdout.text(), exited]);
1505 |   expect(err).not.toContain("panic:");
1506 |   if (!options?.allowErrors) {
1507 |     expect(err).not.toContain("error:");
           
... (truncated)
passes on PR (with fix)
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/cli/install/bun-workspaces.test.ts "test/cli/install/migration/pnpm-lock-v9.test.ts"
bun test v1.4.0 (d3b85cbd0)

test/cli/install/bun-workspaces.test.ts:
(pass) dependency on workspace without version in package.json [2843.60ms]
(pass) workspace member package.json without a name > is skipped (glob) [296.54ms]
(pass) workspace member package.json without a name > is skipped (listed) [266.71ms]
(pass) workspace member package.json without a name > is skipped with a warning when it declares dependencies (glob) [325.13ms]
(pass) allowing negative workspace patterns [398.77ms]
(pass) workspace member package.json without a name > is skipped with a warning when it declares dependencies (listed) [354.96ms]
(pass) workspace member package.json without a name > is skipped (overlapping) [179.45ms]
(pass) workspace member package.json without a name > is skipped with a warning when it declares dependencies (overlapping) [211.22ms]
(pass) workspace member package.json without a name > an empty name counts as no name [195.16ms]
(pass) worksp
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped)
  target       linux-x64-gnu
  build type   Release
  build dir    ./build/release
  revision     d3b85cbd0e
  features     baseline

22 deps, 123 codegen, 1176 objects in 855ms

ninja: Entering directory `/workspace/bun/build/release'
[1/1238] gen ErrorCode+*.h
[2/1238] install /workspace/bun
bun install v1.4.0-canary.1 (b7a043103)

Checked 107 installs across 153 packages (no changes) [18.00ms]
[3/1238] gen bindgenv2
[4/1238] fetch zlib
[zlib] up to date
[5/1238] install /workspace/bun/packages/bun-error
bun install v1.4.0-canary.1 (b7a043103)

Checked 1 install across 2 packages (no changes) [2.00ms]
[6/1238] fetch libjpeg-turbo
[libjpeg-turbo] up to date
[7/1238] fetch tinycc
[tinycc] up to date
[8/1237] gen JSBuffer.lut.h
Generating /workspace/bun/build/release/codegen/JSBuffer.lut.h from /workspace/bun/src/jsc/bindings/JSBuffer.cpp
[9/1237] gen ProcessBindingConstants.lut.h
Generating /workspace/bun/build/release/codegen/ProcessBindingConstants.lut.h from /workspace/bun/src/jsc/bindings/ProcessBindingConstants.cpp
[10/1237] install /workspace/bun/src/node-fallbacks
bun install
... (truncated)
diff hotspot
docs/pm/workspaces.mdx                             |   2 +
 src/install/error.rs                               |   3 -
 src/install/lockfile/Package.rs                    |   2 +
 src/install/lockfile/Package/WorkspaceMap.rs       | 151 +++++++++++++--------
 src/install/migration.rs                           |   5 -
 src/install/pnpm.rs                                |  10 +-
 test/cli/install/bun-workspaces.test.ts            |  90 ++++++++++++
 .../migration/__snapshots__/migrate.test.ts.snap   |  22 ++-
 test/cli/install/migration/npm-arborist/README.md  |   2 +-
 .../install/migration/npm-arborist/fixtures.json   |   2 +-
 test/cli/install/migration/pnpm-lock-v9.test.ts    | 105 ++++++++++++++
 11 files changed, 322 insertions(+), 72 deletions(-)

gate history · 1 passed · 0 rejected · iteration 0

evidence per changed file
file                                                      reads  edits  tests
docs/pm/workspaces.mdx                                        1      1      0
src/install/error.rs                                          2      3      0
src/install/lockfile/Package.rs                               2      1      0
src/install/lockfile/Package/WorkspaceMap.rs                 11     28      0
src/install/migration.rs                                      1      1      0
src/install/pnpm.rs                                           6      3      0
test/cli/install/bun-workspaces.test.ts                       2     10      0
…li/install/migration/__snapshots__/migrate.test.ts.snap      0      0      0
test/cli/install/migration/npm-arborist/README.md             1      1      0
test/cli/install/migration/npm-arborist/fixtures.json         1      1      0
test/cli/install/migration/pnpm-lock-v9.test.ts               4      6      0

A package.json matched by a workspaces pattern (or listed explicitly) that
has no usable "name" failed the whole install with
'Missing "name" from package.json', and a pnpm-lock.yaml importer without
one failed the migration. pnpm and npm accept such members; monorepos
commonly match test fixtures like {"type":"module"} (vite has 23).

Workspaces are linked and resolved by name, so a nameless member is not a
workspace package: the scan and the pnpm migration now skip it, the same
way the scan already skipped "name": "". When the skipped package.json
declares dependencies, the root package.json parse warns once that they
are not installed. Because the scan itself stays quiet, the lockfile
migrations, --filter and the walk that locates the root from a member
directory (which previously also broke on a nameless sibling) do not
repeat the warning.
@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 6 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: 23862f5c-7faf-45e7-a0b7-18ac185c4663

📥 Commits

Reviewing files that changed from the base of the PR and between 4bf3f36 and d3b85cb.

⛔ Files ignored due to path filters (1)
  • test/cli/install/migration/__snapshots__/migrate.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (10)
  • docs/pm/workspaces.mdx
  • src/install/error.rs
  • src/install/lockfile/Package.rs
  • src/install/lockfile/Package/WorkspaceMap.rs
  • src/install/migration.rs
  • src/install/pnpm.rs
  • test/cli/install/bun-workspaces.test.ts
  • test/cli/install/migration/npm-arborist/README.md
  • test/cli/install/migration/npm-arborist/fixtures.json
  • test/cli/install/migration/pnpm-lock-v9.test.ts

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

@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 10:05 PM PT - Aug 14th, 2026

@robobun, your commit d3b85cb is building: #97050

@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

Status: ready for review; the diff is green on every CI lane that ran.

Build 97050 (head d3b85cb) is red only because both darwin 14 aarch64 - test-bun shards never got an agent: every instance expired unrun after an hour in the queue (03:05 through 06:05 UTC) and Buildkite has stopped retrying them. That queue is backed up for every build right now (roughly 190 of these jobs scheduled against 6 agents), so it is unrelated to this change; every other test and build job passed. Retrying those two jobs once the queue drains completes the build: https://buildkite.com/bun/bun/builds/97050#01a00398-972a-4935-a58d-910232a66974 and https://buildkite.com/bun/bun/builds/97050#01a003cf-8d93-4b48-bb13-7e23187b9052

Reproduced on the released binary with the minimal tree from the report (root with workspaces: ["pkgs/*"], pkgs/a named, pkgs/fixture = {"type":"module"}): bun install exits 1 with error: Missing "name" from package.json in pkgs/fixture/package.json; the same shape as a pnpm-lock.yaml importer fails the migration with missing workspace name. With this branch both install/migrate, the fixture is left out of bun.lock and node_modules, and the new tests in test/cli/install/bun-workspaces.test.ts and test/cli/install/migration/pnpm-lock-v9.test.ts fail on the released binary and pass here.

This is also what #6317 reports (nameless package.json files under packages/features/* failing the install), so the description closes it.

On vite v8.2.1, migration now gets past its 23 nameless importers and stops at the relative link: dependencies tracked in #23026, which is independent of this change.

@github-actions

Copy link
Copy Markdown
Contributor

Found 1 issue this PR may fix:

  1. Ignore package.json files without "name" property #6317 - Reporter uses nameless package.json files as folder metadata under packages/features/*; every one triggers Missing "name" from package.json in ... and fails the whole install. This PR removes that error and skips such members, which is exactly what the issue asks for.

If this is helpful, copy the block below into the PR description to auto-close these issues on merge.

Fixes #6317

🤖 Generated with Claude Code

@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

Checked #6317: it is the same failure (nameless package.json files matched by the workspaces patterns each logging Missing "name" from package.json and failing the install), and the repro shape there is covered by the listed-path and glob cases in test/cli/install/bun-workspaces.test.ts. Added Fixes #6317 to the description. It is not a regression test candidate since bun has rejected these since the issue was filed, so the tests stay in the existing workspace and migration files.

Comment thread src/install/lockfile/Package/WorkspaceMap.rs
Comment thread src/install/lockfile/Package/WorkspaceMap.rs Outdated
Comment thread src/install/lockfile/Package/WorkspaceMap.rs Outdated
Comment thread src/install/lockfile/Package/WorkspaceMap.rs Outdated
Comment thread src/install/pnpm.rs Outdated
Comment thread src/install/lockfile/Package/WorkspaceMap.rs Outdated

@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 re-reviewed after 9e3b0cb/5e20ae5a/d3b85cbd — the duplicate-warning nit is fixed (the overlapping test variant asserts exactly one warning) and the comment-cop items are addressed; no new issues found. Since this is a user-facing bun install behavior change with a deliberate design choice (skip + warn rather than derive a name like npm, and unlike pnpm/npm the skipped member's dependencies are not installed), a maintainer sign-off on that direction would still be worthwhile.

Checked: warn_skipped is called from Package::parse after process_names_array returns, so the warnings don't trip the orig_msgs_len != log.msgs.len() error gate inside the scan; the pnpm migrator's continue on a nameless importer leaves it out of workspace_paths so later passes never see it; the removed MissingPackageName/WorkspaceNameMissing variants have no remaining references.

Extended reasoning...

Overview

The PR changes bun install to skip workspace members whose package.json has no "name" (or an empty/non-string one) instead of failing the whole install with Missing "name" from package.json. It touches src/install/lockfile/Package/WorkspaceMap.rs (the scan now returns a Scanned enum and records nameless-with-deps members for a deferred warning), src/install/lockfile/Package.rs (calls warn_skipped from the root parse), src/install/pnpm.rs and src/install/migration.rs (pnpm importers with no name are skipped; the WorkspaceNameMissing error and its message arm are removed), src/install/error.rs (MissingPackageName removed), docs/pm/workspaces.mdx, and adds tests to bun-workspaces.test.ts, pnpm-lock-v9.test.ts, and updates the arborist workspaces-need-update snapshot/notes.

Security risks

None identified. The change relaxes an error into a skip on locally-controlled package.json files; nothing new is read, resolved, linked into node_modules, or executed for the skipped directory. No untrusted-input parsing paths are widened.

Level of scrutiny

High. This is a user-facing behavior change to the package manager — critical-path code — and encodes a design decision that intentionally diverges from npm (which derives a name from the directory) and from pnpm/npm on one axis (they install the nameless member's dependencies; bun does not, and warns instead). The PR description argues the tradeoff well (derived names collide — five of vite's fixtures share the directory name nested; pnpm doesn't link them either), but a maintainer should confirm the direction.

Other factors

My earlier inline finding (duplicate warnings under overlapping workspaces patterns) was fixed in 9e3b0cb with a linear dedup and covered by the new overlapping test variant asserting exactly one warning. Comment-cop feedback was addressed in 5e20ae5/d3b85cbd. Test coverage is thorough: glob/listed/overlapping patterns × with/without dependencies, empty-string name, install from a member directory (root-finding walk), pnpm-lock.yaml migration + frozen install agreeing with the scan, and the arborist workspaces-need-update fixture now migrating. I verified the warning emission point sits after process_names_array returns, so the added log warnings don't hit that function's orig_msgs_len error gate; and that the pnpm migrator's continue keeps the nameless importer out of lockfile.workspace_paths so downstream passes are unaffected. Given the scope, deferring to a human for the design sign-off rather than approving.

@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

Agreed that the direction is the thing to sign off on. The two choices are spelled out in the Fix section: skipping instead of deriving a name (derived names would be linked into node_modules and collide, e.g. five of vite's fixtures live in a directory called nested), and warning instead of installing a nameless member's dependencies (bun has no lockfile representation for a package without a name; the warning only fires when such a package.json actually declares dependencies, so vite-style fixtures are silent). If the preference is to go further and install their dependencies as well, that needs a lockfile representation for nameless workspaces and would be a separate change on top of this one.

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.

Ignore package.json files without "name" property

2 participants