Skip to content

sys: map getcwd ENOENT to CurrentWorkingDirectoryUnlinked#32357

Open
robobun wants to merge 3 commits into
mainfrom
farm/fecdab1d/fix-cwd-unlinked-error
Open

sys: map getcwd ENOENT to CurrentWorkingDirectoryUnlinked#32357
robobun wants to merge 3 commits into
mainfrom
farm/fecdab1d/fix-cwd-unlinked-error

Conversation

@robobun

@robobun robobun commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

What

Restore the actionable "The current working directory was deleted..." hint when bun is started from a directory that no longer exists.

Repro

mkdir /tmp/gone && cd /tmp/gone && rmdir /tmp/gone && bun -e 'console.log(1)'

Before:

ENOENT: Bun could not find a file, and the code that produces this error is missing a better error.

After:

error: The current working directory was deleted, so that command didn't work. Please cd into a different directory and try again.

Cause

crash_handler::handle_root_error has an arm for bun_core::err!("CurrentWorkingDirectoryUnlinked") (ported from the Zig error.CurrentWorkingDirectoryUnlinked case), but nothing ever produced that name. Zig's std.posix.getcwd maps ENOENT to error.CurrentWorkingDirectoryUnlinked at the syscall wrapper; the Rust bun_sys::getcwd returns a plain bun_sys::Error{errno: ENOENT, syscall: Tag::getcwd}, and both From<bun_sys::Error> for bun_core::Error and bun_sys::Error::to_zig_err() discarded the syscall tag and interned the bare errno name "ENOENT". bun_core::getcwd went through std::io::Error with the same result. The error then fell through to the generic ENOENT arm in handle_root_error.

Fix

  • bun_sys::Error::to_zig_err: when syscall == Tag::getcwd && errno == ENOENT, return err!("CurrentWorkingDirectoryUnlinked").
  • From<bun_sys::Error> for bun_core::Error: route through to_zig_err() so ? conversions pick up the same mapping.
  • bun_core::getcwd (which calls libc::getcwd directly and bypasses bun_sys::Error): check for ENOENT and return the named error.

Verification

New tests in test/cli/run/run-crash-handler.test.ts cover bun -e and bun run from a deleted cwd (POSIX only; Windows refuses to remove a process's cwd). Updated the existing compiled-binary test in test/bundler/bun-build-compile.test.ts which routes through the same handle_root_error path and now sees the better message.

Zig's std.posix.getcwd maps ENOENT to error.CurrentWorkingDirectoryUnlinked,
which crash_handler::handle_root_error recognizes and turns into the
actionable hint "The current working directory was deleted...". The Rust
bun_sys::Error -> bun_core::Error conversion dropped the syscall tag and
emitted a bare ENOENT, so the hint was dead code and users saw the generic
"Bun could not find a file" fallback.

Route From<bun_sys::Error> through to_zig_err() and special-case
(Tag::getcwd, ENOENT) there; apply the same mapping in bun_core::getcwd
which bypasses bun_sys::Error entirely.
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

When getcwd fails with ENOENT (deleted working directory), Bun now maps the error to a named CurrentWorkingDirectoryUnlinked code instead of a generic errno. This mapping is applied in both the Rust util.rs getcwd path and the Error::to_zig_err conversion, with From<Error> routed through to_zig_err. Two test files update assertions accordingly.

Changes

Deleted-CWD error mapping and test coverage

Layer / File(s) Summary
getcwd+ENOENT → CurrentWorkingDirectoryUnlinked mapping
src/bun_core/util.rs, src/sys/Error.rs, src/sys/lib.rs
util.rs captures last_os_error() and returns CurrentWorkingDirectoryUnlinked when errno is ENOENT. Error::to_zig_err adds a branch for Tag::getcwd + ENOENT returning the Zig error name. From<Error> for bun_core::Error is updated to route through to_zig_err() instead of from_errno directly.
Test assertions for human-readable deleted-CWD message
test/bundler/bun-build-compile.test.ts, test/cli/run/run-crash-handler.test.ts
The existing compiled-binary deleted-cwd test replaces its ENOENT substring check with The current working directory was deleted. A new POSIX-only suite in the crash-handler tests deletes the cwd before bun starts and asserts the actionable hint appears while the generic missing-file fallback does not.

Possibly related PRs

  • oven-sh/bun#31496: Ensures the ENOENT error from a deleted cwd is propagated rather than crashing; this PR builds on that by refining the errno-to-named-error mapping for getcwd+ENOENT and updating the corresponding test assertions.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: mapping getcwd ENOENT errors to a more specific error type for better user experience.
Description check ✅ Passed The description is comprehensive and well-structured, covering What, Repro, Cause, Fix, and Verification sections beyond the minimal template requirements.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@github-actions

Copy link
Copy Markdown
Contributor

Found 1 issue this PR may fix:

  1. Bun Shell throws if current working directory is deleted #23589 - Bun Shell throws when the current working directory is deleted, which is exactly the getcwd ENOENT scenario this PR now handles with a proper error message

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

Fixes #23589

🤖 Generated with Claude Code

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test/cli/run/run-crash-handler.test.ts (1)

94-121: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Consider making these tests concurrent.

The tests spawn subprocesses and use temp directories, so they should use test.concurrent per the guideline to prefer concurrent tests when spawning processes or doing file I/O.

♻️ Refactor options

Option 1: Add .concurrent to existing tests

   for (const cmd of [
     ["-e", "console.log(1)"],
     ["run", "foo"],
   ]) {
-    test(`bun ${cmd[0]} prints the cwd-deleted hint`, async () => {
+    test.concurrent(`bun ${cmd[0]} prints the cwd-deleted hint`, async () => {

Option 2: Use test.concurrent.each (more idiomatic)

-  for (const cmd of [
-    ["-e", "console.log(1)"],
-    ["run", "foo"],
-  ]) {
-    test(`bun ${cmd[0]} prints the cwd-deleted hint`, async () => {
+  test.concurrent.each([
+    [["-e", "console.log(1)"]],
+    [["run", "foo"]],
+  ])(`bun %s prints the cwd-deleted hint`, async (cmd) => {
       using dir = tempDir("cwd-unlinked", {});
       const gone = String(dir);

       await using proc = Bun.spawn({
         cmd: [
           "/bin/sh",
           "-c",
           `cd "${gone}" && rmdir "${gone}" && exec "${bunExe()}" ${cmd.map(a => `'${a}'`).join(" ")}`,
         ],
         env: bunEnv,
         stdout: "pipe",
         stderr: "pipe",
       });
       const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

       expect({ stdout, stderr, exitCode }).toEqual({
         stdout: "",
         stderr: expect.stringContaining("The current working directory was deleted"),
         exitCode: 1,
       });
       expect(stderr).not.toContain("Bun could not find a file");
     });
-  }
🤖 Prompt for 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.

In `@test/cli/run/run-crash-handler.test.ts` around lines 94 - 121, The test cases
in this block spawn subprocesses and use temp directories, which should run
concurrently per the testing guidelines. Refactor the for loop iterating over
the cmd array by replacing the standard test function calls with
test.concurrent.each to make these tests concurrent, or alternatively add the
.concurrent modifier to the existing test function call. The idiomatic approach
is to use test.concurrent.each which will handle both the iteration and
concurrent execution of the test cases.
🤖 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.

Outside diff comments:
In `@test/cli/run/run-crash-handler.test.ts`:
- Around line 94-121: The test cases in this block spawn subprocesses and use
temp directories, which should run concurrently per the testing guidelines.
Refactor the for loop iterating over the cmd array by replacing the standard
test function calls with test.concurrent.each to make these tests concurrent, or
alternatively add the .concurrent modifier to the existing test function call.
The idiomatic approach is to use test.concurrent.each which will handle both the
iteration and concurrent execution of the test cases.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4a2c4406-2c27-4871-a8f2-4ef06abac029

📥 Commits

Reviewing files that changed from the base of the PR and between 406e57f and 03ccb66.

📒 Files selected for processing (1)
  • test/cli/run/run-crash-handler.test.ts

@robobun

robobun commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 4:29 PM PT - Jun 15th, 2026

@robobun, your commit 990dd053cdddfd023e59bf4ccdde7e0d28d5e6c8 passed in Build #62662! 🎉


🧪   To try this PR locally:

bunx bun-pr 32357

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

bun-32357 --bun

@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.

LGTM — small, well-scoped error-name mapping that restores the Zig-era cwd-deleted hint, with tests.

Extended reasoning...

Overview

This PR restores the actionable "current working directory was deleted" message when bun starts in an unlinked cwd. It touches three Rust files and two test files:

  • src/sys/Error.rs: adds a single branch in to_zig_err() so Tag::getcwd + ENOENT returns the interned CurrentWorkingDirectoryUnlinked name.
  • src/sys/lib.rs: From<bun_sys::Error> for bun_core::Error now calls e.to_zig_err() instead of bun_core::Error::from_errno(e.errno as i32). Since to_zig_err()'s fallback is errno_to_err(self.errno)bun_core::Error::from_errno(errno as i32), this is behaviorally identical for every existing error and only adds the one new mapping.
  • src/bun_core/util.rs: getcwd() (which calls libc::getcwd directly and bypasses bun_sys::Error) now captures last_os_error() once, checks for ENOENT, and returns the named error; all other errnos go through the existing .into() path unchanged.
  • Tests: a new POSIX-only describe in run-crash-handler.test.ts exercises bun -e and bun run from a deleted cwd, and the existing compiled-binary deleted-cwd test in bun-build-compile.test.ts updates its stderr assertion from ENOENT to the human-readable hint.

I confirmed the consumer side matches: crash_handler::handle_root_error already has an arm comparing against bun_core::err!("CurrentWorkingDirectoryUnlinked") that prints exactly the string the tests assert on.

Security risks

None. This only refines which interned error name is produced for one (syscall, errno) pair on an error path that already terminates the process. No new I/O, no parsing, no auth/permissions/crypto, no user-controlled data flowing anywhere new.

Level of scrutiny

Low. This is an error-message/UX fix that re-establishes parity with the Zig stdlib's std.posix.getcwd ENOENT→error.CurrentWorkingDirectoryUnlinked mapping. The only change with broad reach — the From impl — is a pure refactor to call an existing helper whose fallback is byte-for-byte equivalent to the old body, so there's no collateral behavior change for other errnos or syscalls.

Other factors

The bug hunter found no issues. There are no outstanding human review comments. New tests directly cover the regression (asserting both that the hint appears and that the generic "Bun could not find a file" fallback does not), and an existing test was updated rather than left stale. The change is small, mechanical, and well-commented.

@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.

LGTM — focused error-name mapping that restores the existing cwd-deleted hint, with tests.

Extended reasoning...

Overview

This PR restores the actionable "current working directory was deleted" message when bun starts from an unlinked cwd. It touches three Rust files (src/bun_core/util.rs, src/sys/Error.rs, src/sys/lib.rs) to map getcwd+ENOENT to the interned error name CurrentWorkingDirectoryUnlinked, which crash_handler::handle_root_error already matches on (verified at src/crash_handler/lib.rs:1341). The From<bun_sys::Error> impl now routes through to_zig_err(), whose fallthrough is identical to the old from_errno call, so non-getcwd errors are unaffected. Two test files are updated: an existing compiled-binary deleted-cwd assertion is tightened, and a new POSIX-only suite covers bun -e / bun run.

Security risks

None. This is purely an errno→error-name refinement on an existing failure path; no new inputs, no auth/crypto/permissions, no externally controllable data.

Level of scrutiny

Low. The change is small (~20 LOC of logic), mechanical, and mirrors the documented Zig stdlib behavior it was ported from. The target error name and consumer already exist in the codebase — this just wires up the producer that went missing during the Zig→Rust port. Worst-case regression would be a slightly different error string on a startup failure path that already exits 1.

Other factors

The bug-hunting system found no issues. Test coverage is solid: the new tests assert both the presence of the hint and the absence of the generic fallback, and the existing compile test was updated rather than left stale. No outstanding human review comments on the PR.

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.

1 participant