Repository navigation
Conversation
A workspaces glob that fails to open or stat a path now reports the syscall and the absolute path. The error is logged once. The CLI no longer prints 'An internal error occurred (GlobError)' after it. The glob walker reports the whole path when the stat of a literal last component fails, not only the component.
|
Warning Review paused — included plan limit reachedKeep your review moving with free on-demand reviews.
On-demand reviews are free for one more day.
Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing. Promotion and pricing detailsOn-demand reviews are free for one more day. After that, they cost $0.25 per reviewed file. Review limit detailsOr wait 3 minutes for your next included review. Limit details: You’ve used all 10 included reviews currently available. Review configuration: ⚙️ Run configurationConfiguration used: Repository: oven-sh/bun/.coderabbit.yaml Review profile: ASSERTIVE Plan: Essentials Run ID: 📒 Files selected for processing (6)
Comment |
|
Updated 2:56 PM PT - Sep 19th, 2026
✅ @robobun, your commit eb7a0bbc6e50a6dee1cf30d3ecb33d0312f67584 passed in 🧪 To try this PR locally: bunx bun-pr 43517That installs a local version of the PR into your bun-43517 --bun |
|
Status Reproduced on Linux x64 with canary 1.4.3-canary.1 (367d939). Run it as a user that is not root, because root ignores the mode bits: d=$(mktemp -d) && cd $d
mkdir -p pkgs/a pkgs/secret
echo '{"name":"a","version":"1.0.0"}' > pkgs/a/package.json
echo '{"name":"root","workspaces":["pkgs/*"]}' > package.json
chmod 000 pkgs/secret
bun installWith this PR there is one error line, and it names the directory. The exit code is still 1: |
There was a problem hiding this comment.
Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.
Beyond the inline finding, I also checked two things: the new fallible self.walker.join(...)? at src/glob/GlobWalker.rs:676 runs after close_disallowing_cwd(fd), so an allocation failure there does not leak the directory fd, and it mirrors the existing e.with_path(matched_as_slice::<SENTINEL>(..)) shape at line 439; and no references to Error::GlobError remain anywhere under src/ after the variant's removal.
Extended reasoning...
The diff is small and mostly a refactor of three duplicated error blocks in src/install/lockfile/Package/WorkspaceMap.rs into add_glob_error, plus a one-site change in the glob walker's literal fast path so the stat error carries the joined directory + literal path. I confirmed the walker change is placed after the fd is closed (no fd leak on the ? path), follows the same pattern as the sibling open error site in the same file, and that the removed GlobError variant has no remaining consumers. The confirmed inline finding about sync_lockfile dropping the scratch log (silent exit 1 on bun add/bun update write-back) is the one substantive behavior change beyond the message text and is the reason a human should weigh in; the other ruled-out candidates (dead Err arm for walker construction, Windows separator mixing, path-buffer overflow fallback) are cosmetic or unreachable.
bun add and bun update parse the edited package.json files a second time into a scratch log. That log was dropped on an error, so a workspaces glob that fails there exited with code 1 and no message.
There was a problem hiding this comment.
LGTM — the follow-up commit resolves the write-back concern from my earlier review: sync_lockfile now prints the scratch log before propagating, and the new bun add test exercises exactly that path.
What was reviewed:
add_glob_error:join_abs_string_buf_checkedfalls back to the rawerr.pathon overflow rather than unwrapping;InstallFailedis handled byhandle_errorininstall_command.rsandpm_update_package_json.rs, so the message prints once and the "internal error" line is gone. No remainingGlobErrorreferences insrc/ortest/.GlobWalkerliteral fast path:self.walker.joinalready goes through?on the siblingprepare_matched_pathcall, andjoin_sep_maybe_zskips an emptydir_path, so a pattern with no directory prefix does not gain a leading separator.- Write-back: the scratch
Logis distinct fromCli::LOG_, so printing it insync_lockfiledoes not double-print withhandle_error.
Extended reasoning...
Overview
The PR touches error reporting in src/install/lockfile/Package/WorkspaceMap.rs (three duplicated add_error_pretty! sites collapsed into add_glob_error, which joins err.path onto cwd and returns InstallFailed), removes the now-unused Error::GlobError variant from src/install/error.rs, changes the literal fast path in src/glob/GlobWalker.rs to report dir_path + literal instead of the bare literal, and splits sync_lockfile in src/install/PackageManager/package_json_write_back.rs so the scratch log's errors are printed to Output::error_writer() before the error propagates. Tests in test/cli/install/bad-workspace.test.ts and test/js/bun/glob/scan.test.ts assert exact error lines including syscall and path, and a local Bun.serve registry test verifies the write-back failure is now reported.
Security risks
None identified. The change only affects diagnostic output. The joined path is derived from the walker's own dir_path and pattern component, and from the install cwd; nothing user-controlled is interpreted as a path in a new way. The join_abs_string_buf_checked call is bounds-checked and falls back to the unjoined path on overflow.
Level of scrutiny
Moderate. It is a small, self-contained error-reporting change. The points that warranted verification were: (1) the ? on self.walker.join in the walker fast path — the enclosing transition_to_dir_iter_state returns Result<Maybe<()>, Error> and the sibling prepare_matched_path(...)? at line 689 already relies on the same AllocError conversion; (2) an empty dir_path for a root-level literal — join_sep_vec skips empty parts so no leading separator is introduced; (3) the possibility of double-printing in the write-back path — the ScratchManifests log is its own Log instance, separate from Cli::LOG_ that handle_error prints, so there is no duplication; (4) the removed enum variant — no references remain in src/ or test/. No CODEOWNERS entry covers the changed files.
Other factors
The second commit (eb7a0bbc) directly addresses the one inline finding from my earlier review, and adds a targeted test for it that deletes the glob directory between the two walks via the registry handler, which is deterministic (the manifest request always precedes the write-back). The bug hunt ran to a dry streak with no findings. The test.concurrent.skipIf(...).each(...) chain is already used elsewhere in the suite (sql-connection-socket-uaf.test.ts). The chmod-based tests are gated on non-Windows and non-root, matching existing permission tests in test/cli/install.
Problem
bun installfails when aworkspacesglob reaches a directory that the user cannot open. The error does not name the directory, and a second line blames bun:process_names_array(src/install/lockfile/Package/WorkspaceMap.rs) prints only the errno and returnsError::GlobError. The CLI has no case for that error, sohandle_root_erroradds the second line.package.jsonwhen the stat of a literal last component fails (src/glob/GlobWalker.rs).sync_lockfile(package_json_write_back.rs) drops the log of its parse, sobun addprints only theGlobErrorline.Fix
... due to error EACCES (open "/repo/pkgs/secret").InstallFailed, as the rest ofprocess_names_arraydoes after it logs. Nothing else usedGlobError, so it is removed.sync_lockfileprints its log when its parse fails.bad-workspace.test.ts,scan.test.ts("literal fast path"), and thebun-add,bun-update,bun-workspacessuites. Self-reviewed: 9 concerns, 4 addressed, 5 not caused by this change (Notes).Background
GlobWalkerwalks aworkspacesglob withpackage.jsonappended, for examplepkgs/*/package.json.InstallFailedthe CLI prints the log and exits with code 1.bun addresolves,sync_lockfileparses the edited package.json files again, with its own log. That parse walks the globs a second time.Notes
Origin. The review of #43404 (a workspace root at
/) noted that a broad glob such as*meets/rootand/lost+foundas a user that is not root. The same failure happens in an ordinary directory, and on main without that PR. That review suggested to skip directories that fail with EACCES.Why the install still fails. A glob library skips a directory that it cannot read. The package managers do not behave that way end to end. Tree:
pkgs/a/package.json,pkgs/a/nested/b/package.json, andpkgs/secretowned by root with mode 000. The install runs asnobody.workspacesentrypkgs/*secretERR_PNPM_PACKAGE_MANIFEST_IO_ERRORpkgs/*/nested/*secretERR_PNPM_WORKSPACE_WALK_ERRORpkgs/**ERR_PNPM_WORKSPACE_WALK_ERRORnpm's glob ignores the directory it cannot read, but
@npmcli/map-workspacesthen readspkgs/secret/package.jsonand stops on every error except ENOENT and ENOTDIR. A silent skip also changes resolution: a sibling that depends on the skipped package by a plain version range resolves it from the registry. All four tools name the path in their error. bun did not.Output after the change, same tree:
With
chmod 444 pkgs/secret:... due to error EACCES (fstatat "/tmp/ws/pkgs/secret/package.json"). Before the walker change this case reported the pathpackage.json, which reads as the root manifest.new Bun.Glob("*/package.json").scanSync({ cwd })had the samepath: "package.json"and now haspath: "locked/package.json"(absolute withabsolute: true). The shell printedbun: Permission denied: package.jsonand now printsbun: Permission denied: locked/package.json.On Windows the missing-directory case prints
(open "C:\workspace\ws\missing"). I ranbad-workspace.test.tsthere with a debug build.Commands. I ran 29 package manager commands (
install,add,remove,update,patch,outdated,pm ls,why,publish --dry-run,install --frozen-lockfileand others) against a project whose glob fails, some with abun.lockand some without, before and after the change. Every command that reports the glob error now prints it once, with the path, and exits with code 1. No command became silent.Behavior that this change does not cause (raised in self-review, left as it is):
InstallFailed, the same as for every other error this function logs.package-lock.jsonoryarn.lock, the error prints two times: one for the migration attempt, one for the install. Every workspace error on that path does this.PackageManager.rsignores every error fromprocess_names_array, so the member installs as a project of its own.Bun.Globtest.test/cli/installdo.Write-back. Found in review of this PR: with
InstallFailedalone, a glob that fails in the write-back parse exited with code 1 and no message, becausesync_lockfiledropped its log. The test servesbazfrom a local registry that removes the glob's directory when bun asks for the manifest. That is after the first walk and before the write-back walk. Main prints onlyAn internal error occurred (GlobError)there. This branch prints theFailed to run workspace patternerror with the path.Tests. The missing-directory test runs everywhere and fails without the change (two
error:lines, no path). The two EACCES cases and theBun.Globcase need a user that is not root, and skip on Windows, wherechmodcannot make a directory unreadable. I ran them asnobody: they fail with the released build and pass with the debug build. Inscan.test.tsthe six whole-repo recursive scans hit their 30 s timeout under the debug build in my container. They do not reach the changed branch.no test proof · iteration 0 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/js/bun/glob/scan.test.ts, test/cli/install/bad-workspace.test.ts