install: reject an empty root package.json instead of deleting the lockfile
Problem
- With a 0-byte root
package.json,bun installandbun updateprintNo packages! Deleted empty lockfile, deletebun.lockand exit 0. The next install resolves every range again, so every pin is lost. - Cause: the package.json parser (
src/parsers/json.rs) returns{}for empty input, and the install takes that as a manifest with no dependencies.
Fix
exit_on_empty_package_json()(install_with_manager.rs) printserror: failed to parse '<dir>/package.json': file is emptyand a repair note, and exits 1.root_package_json_source()calls it forbun install, before the lockfile is saved or deleted.update_package_json_and_install_with_manager_with_updates()calls it forbun update,bun removeandbun patch, on the contents as read.bun addandbun linkare exempt: they fill the file.- Verified:
bun-install.test.ts, describeroot package.json that cannot be read or parsed. The six new cases fail on 1.4 canary.
Background
save_lockfiledeletes the lockfile when the install has no packages. That is right for a manifest with no dependencies and wrong for a manifest that is not there.- A writer that truncates before it writes leaves the file empty for a moment, or for good when it is interrupted. #39689 makes bun's writers atomic.
Downsides
touch package.json && bun installnow exits 1. Write{}for a project with no dependencies.- An install that reads package.json while a writer has it truncated now fails: 19 of 900 installs beside a
bun pm pkg setloop. Before, it deleted the lockfile in 49 of 2,700. - Cost per install: one length check, no syscall.
Notes
Rebased onto main 9ed8d11b96 on 2026-10-09, no conflicts. The tests of this PR pass on the rebased head with the debug build; the 13 tests of bun-install.test.ts that need bitbucket.org, gitlab.com or another outside host fail on this machine, which has no route to them.
What stays as it was. A package.json that says {} installs nothing and removes the lockfile, as before: the check is on the bytes of the file. A missing package.json already fails, and a whitespace-only one already fails to parse. A 0-byte workspace member already fails on its missing name before anything is installed, on both install paths, and a test pins that. The parser still returns {} for empty files: import "./package.json", packages in node_modules with an empty package.json, bun pm pkg set and bun init depend on that, and the last two repair the file. bun update needed its own call because it reads the file first and prints the parsed root back into the cache as {}, so the check in root_package_json_source() alone saw a non-empty source. npm refuses the same file: npm error code EJSONPARSE ... Unexpected end of JSON input while parsing empty string.
The fixture migration/pnpm/version-number-dot/package.json used an empty file to mean "no dependencies". It now says {}. That test is about the lockfileVersion warning.
Checked on the rebased head (debug build) against the installed 1.4.3-canary.1+367d939d9, with two scripts from the fuzz ledger entry for this bug:
- By steps, with a local registry:
bun.lockpins 1.0.0, the range is^1.0.0, the registry has 1.4.0. package.json is emptied,bun installruns, package.json is restored,bun installruns again. Before: both installs exit 0, the lockfile is deleted in between, and it pins 1.4.0 afterwards. After: the first install exits 1 withfile is empty, the lockfile is untouched, and the second install reports no changes with 1.0.0 still pinned. - Nothing made to fail:
bun installin a loop beside onebun pm pkg setloop in the same project (the writer is the installed bun, which truncates and then writes). Before: the lockfile is deleted in 49 of 2,700 installs. After: in 0 of 900, and 19 of them stop withfile is empty.
no test proof · iteration 1 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/cli/install/bun-install.test.ts