Skip to content

install: name folder, tarball and git packages without a package.json name after their source - #38681

Open
robobun wants to merge 2 commits into
mainfrom
farm/ab57eff1/lockfile-fallback-package-name
Open

install: name folder, tarball and git packages without a package.json name after their source#38681
robobun wants to merge 2 commits into
mainfrom
farm/ab57eff1/lockfile-fallback-package-name

Conversation

@robobun

@robobun robobun commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Problem

  • A file: folder, link: target, local or remote tarball whose own package.json has no name installs, but bun.lock ends up with "x": ["@file:../dir", {}] (or ["@../pkg.tgz", ...], ["@https://...", ...]). The next bun install fails to parse that entry (error: Invalid package resolution), prints warn: Ignoring lockfile and re-resolves everything, bun install --frozen-lockfile always fails with lockfile had changes, but lockfile is frozen, and bun pm ls fails with error: Error loading lockfile: InvalidLockfile. Same result for a git dependency without a package.json name whose repository name is not usable as a package name. This is bun.lock failed on file:../xxx #17060 ("$": ["@file:../srv/gen/js", ...]).
  • A package.json name containing an extra @ (a@b, @scope/a@b) in a folder, tarball, git or workspace package produces ["a@b@file:../dir", {}], which the parser splits into name a and resolution b@file:../dir (error: Missing git dependency tag / Unexpected resolution), with the same consequences.
  • Cause: the bun.lock writer (src/install/lockfile/bun.lock.rs, the "[\"{}@{}\", " writes) emits name@resolution and the reader recovers the name with dependency::split_name_and_version, so an empty name or one with a second @ cannot round-trip. Package::parse_with_json_impl (src/install/lockfile/Package.rs) only derived a name for git packages without one; folder and tarball packages were left unnamed, and names with @ were copied as-is.
  • Also visible without a lockfile: bun add ../nameless-dir wrote "": "../nameless-dir" into package.json and then failed to install, and the isolated linker stored a nameless folder package as node_modules/.bun/@file+pkg/node_modules/ with the package files spilled directly into that node_modules.

Fix

  • ResolverContext::fallback_name() replaces the git-only new_name/set_new_name/take_new_name/resolution()/dep_id() surface. Package::parse_with_json_impl uses it whenever package.json has no (or an empty) name; the git, tarball and folder/link: resolvers implement it, while the root, workspace members (registered by name in WorkspaceMap before this runs) and the npm cache keep returning None, so their behavior is unchanged.
  • dependency::fallback_package_name(location) is the shared derivation: the last path component of the repository, tarball path/URL (query string and .tgz/.tar.gz stripped) or folder, and unnamed-package when that component is not storable either (file:../odd@dir, file:..). It always satisfies the check below, so whatever bun writes it can read back. Git packages get exactly the name they got before for every real repository (Repository::create_dependency_name_from_version_literal is removed; its SHA-1-bytes fallback for an empty repository name is replaced by the shared fallback, which unlike raw SHA-1 bytes is loadable). ResolutionType::ZEROED and the dep_id parameter of process_extracted_tarball_package only served the removed surface and are removed too.
  • Why a derived name rather than rejecting: a local package without a name is legitimate input that npm, pnpm and yarn all install, and bun already did the same for git packages. Deriving it from the resolution (not from the dependency alias) keeps the name identical for every alias that points at the same folder or tarball and on every re-resolve, so get_package_id finds the entry already in the lockfile instead of appending a second package (the bun-lock test adds a second alias for the same folder after the first install and checks it resolves to the same folder-pkg@file:... entry).
  • The parse-time check added by install: reject tarball, folder and git packages whose package.json name is invalid #38633 now uses dependency::is_safe_lockfile_package_name, i.e. is_safe_install_folder_name (what the reader applies) plus "no @ other than a scope marker" (what the name@resolution encoding needs). Names with an extra @ are therefore rejected with the same Invalid package name "a@b" error instead of being invented around, consistent with the a:b case, and this also covers workspace members. This branch contains install: reject tarball, folder and git packages whose package.json name is invalid #38633's commit and is meant to land after it; once it is merged the diff here shrinks to the changes described above.
  • Verified with the debug build, all failing on the released binary for the stated reason and passing with this change:
    • test/cli/install/bun-lock.test.ts: folder, empty-string name, unstorable folder name, local tarball, remote tarball and remote tarball with a query string are written as folder-pkg@file:pkgs/folder-pkg, unnamed-package@file:pkgs/odd@folder, tarball-pkg@./tarball-pkg.tgz, remote-pkg@http://..., signed-pkg@http://...?token=abc/def; a second install and --frozen-lockfile load the file unchanged without any warning, and a second alias for the same folder reuses the entry.
    • test/cli/install/bun-install-git-deps.test.ts: nameless git package from shared-repo.git keeps the name shared-repo.git; from odd@repo.git it becomes unnamed-package; --frozen-lockfile loads the result.
    • test/cli/install/bun-install.test.ts: a@b folder, @scope/a@b tarball and a@b workspace member are rejected, added next to install: reject tarball, folder and git packages whose package.json name is invalid #38633's tests.
    • test/cli/install/isolated-install.test.ts: nameless folder dependency is stored as .bun/pkg-1@file+pkg-1/node_modules/pkg-1 and require() works.
    • test/cli/install/bun-add.test.ts: bun add file:../x/pkg-without-name adds "pkg-without-name" and installs it.
    • Full runs of bun-lock, bun-install-git-deps, isolated-install, bun-add and bun-install (remaining bun-install failures are the bitbucket/gitlab network tests plus two tests that fail identically without this change in this environment).
  • Intentionally unchanged: the root package (exempt from the name check, as in install: reject tarball, folder and git packages whose package.json name is invalid #38633; a root named a@b only hits this encoding if something depends on it with file:.), and verify_package_json_name_and_version, which already reinstalls packages whose package.json has no name on every run and keeps doing so.

Background

  • bun.lock's packages section stores each installed package as "<path in node_modules>": ["<name>@<resolution>", ...]. On load the first element is split with split_name_and_version: at the first @, or at the second one when the string starts with @ (a scope). The recovered name is then checked with is_safe_install_folder_name, because package names from the lockfile become folder names (npm cache, isolated store). A single entry failing either step makes the whole lockfile unloadable, which bun reports as Ignoring lockfile.
  • Package::parse_with_json_impl turns a package.json into a lockfile Package for everything that is not a registry manifest: the root, workspace members, file: folders, link: targets, and the package.json extracted from tarball and git dependencies. It runs in two passes over a StringBuilder (count the bytes, allocate once, then append), which is why the name is decided once up front and appended later.
  • ResolverContext is the per-source strategy object passed into that function (GitResolver, TarballResolver, NewResolver<Folder|Symlink|Workspace>, CacheFolderResolver, () for the root); it supplies the package's Resolution, and now also the name to use when package.json has none.
  • The package name is used as the key of lockfile.package_index; a dependency that resolves to a folder or tarball already present in the lockfile is matched through get_package_id(name_hash, ..., resolution), so the name bun derives has to be the same every time the same source is resolved.

Fixes #17060

…ame is invalid

Package::parse copied the name out of a non-root package's package.json
verbatim, so a tarball, folder, git or workspace package named e.g. "a:b"
was written to bun.lock as "a:b@<resolution>". The bun.lock parser rejects
such names ("Invalid package name"), so every following install ignored the
lockfile and rewrote it, --frozen-lockfile always failed and bun pm ls
failed with InvalidLockfile.

Apply the lockfile parser's check (dependency::is_safe_install_folder_name)
when the name is read, log an error pointing at the name in that
package.json, and fail the install before anything is saved. The root
package is exempt: its name is not a bun.lock packages entry.

The tarball and git arms of process_extracted_tarball_package exit through
PackageManager::crash so the logged reason is printed before exiting.
… name after their source

bun.lock stores a package as "<name>@<resolution>" and splits it apart again
at the first "@" after an optional scope, so an empty name ("@file:../dir")
or a name containing another "@" is written out but cannot be loaded back:
every following install prints "Ignoring lockfile" and --frozen-lockfile
always fails.

Replace the git-only new_name hook on ResolverContext with fallback_name(),
implemented by the git, tarball and folder/link resolvers: a package whose
package.json has no name is named after the last component of its
repository, tarball URL or folder (dependency::fallback_package_name), or
"unnamed-package" when that is not storable either. Git packages keep the
name they were given before; the SHA-1 fallback for an empty repository
name is replaced by the shared one.

The parse-time name check now uses is_safe_lockfile_package_name, which
also rejects names with an extra "@", so folder, tarball, git and
workspace packages with such a name fail to install instead of saving a
lockfile that never loads.
@coderabbitai

coderabbitai Bot commented Aug 14, 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: 31 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: 778e8201-ab26-41c3-a2d1-ae7c746c2378

📥 Commits

Reviewing files that changed from the base of the PR and between 9cff2a1 and 0847a60.

📒 Files selected for processing (12)
  • src/install/PackageManager/processDependencyList.rs
  • src/install/PackageManager/runTasks.rs
  • src/install/dependency.rs
  • src/install/lockfile/Package.rs
  • src/install/repository.rs
  • src/install/resolution.rs
  • src/install/resolvers/folder_resolver.rs
  • test/cli/install/bun-add.test.ts
  • test/cli/install/bun-install-git-deps.test.ts
  • test/cli/install/bun-install.test.ts
  • test/cli/install/bun-lock.test.ts
  • test/cli/install/isolated-install.test.ts

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

@robobun

robobun commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author

Status: reproduced on bun 1.4.0 with file: folders, link: targets, local and remote tarballs whose package.json has no name (bun.lock gets "x": ["@file:../dir", {}], the next bun install prints warn: Ignoring lockfile, --frozen-lockfile fails, bun pm ls fails with InvalidLockfile), and with names containing an extra @ in folder, tarball, git and workspace packages (["a@b@file:../dir", {}]).

Fix is in this PR (#38681). It is stacked on #38633, whose commit is included here: #38633 rejects invalid names, this PR derives a name for packages that have none and extends the rejection to names the name@resolution encoding cannot hold.

Tests: test/cli/install/bun-lock.test.ts, bun-install-git-deps.test.ts, bun-install.test.ts (next to #38633's cases), isolated-install.test.ts, bun-add.test.ts; each new test fails on the released binary and passes with the debug build.

Comment thread src/install/PackageManager/processDependencyList.rs
@github-actions

Copy link
Copy Markdown
Contributor

Found 2 issues this PR may fix:

  1. bun.lock failed on file:../xxx #17060 - A file:../srv/gen/js folder dependency whose package.json has no name is written to bun.lock as ["@file:../srv/gen/js", ...], and the next install fails with exactly the error: Invalid package resolution + warn: Ignoring lockfile this PR eliminates by deriving a fallback name.
  2. Bun randomly generates an invalid lockfile when using tarball URLs #13861 - A remote tarball URL dependency produces a lockfile entry with an empty package name (a bare : key in the yarn-format output); this PR names tarball packages after their URL basename, though it may rename rather than deduplicate the entry.

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

Fixes #17060
Fixes #13861

🤖 Generated with Claude Code

@robobun

robobun commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author

#17060 is exactly the folder case fixed here ("$": ["@file:../srv/gen/js", ...] followed by Invalid package resolution / Ignoring lockfile); added Fixes #17060 to the description.

#13861 is not claimed. Its yarn.lock shows a bare : entry next to a correctly named daypilot-pro-react@... entry, i.e. a second package record with an empty name and version for a tarball that does have a name. A tarball without a name on current main produces a single "@<url>": entry (which this PR turns into "<basename>@<url>":), so the shape in that report comes from something else.

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.lock failed on file:../xxx

1 participant