getcwd: report CurrentWorkingDirectoryUnlinked from a deleted cwd again

Merged
RO
3mo ago
+50-9

Problem

  • Starting bun from a directory that has been deleted prints the generic fallback instead of the hint that tells the user what to do:
    ENOENT: Bun could not find a file, and the code that produces this error is missing a better error.
    
  • crash_handler::handle_root_error (src/crash_handler/lib.rs) already has an arm for the error name CurrentWorkingDirectoryUnlinked that prints The current working directory was deleted, so that command didn't work. Please cd into a different directory and try again., but nothing produces that name, so the arm is dead code.
  • Cause: before the port, std.posix.getcwd mapped ENOENT to error.CurrentWorkingDirectoryUnlinked. The call sites that feed handle_root_error now use bun_sys::getcwd, whose error collapses to a bare SystemErrno::ENOENT, and bun_core's own getcwd returned Unexpected.
  • Repro (POSIX): mkdir /tmp/gone && cd /tmp/gone && rmdir /tmp/gone && bun install (or bun test, or a bun build --compile binary). bun run / bun -e are unaffected: they fall back to the executable's directory and boot.

Fix

  • bun_core::Error gains a CurrentWorkingDirectoryUnlinked variant; its name() is the string handle_root_error matches on.
  • bun_core's getcwd_len returns that variant when errno == ENOENT, making bun_core::getcwd the one producer of the name, as std.posix.getcwd was before.
  • The three getcwd calls whose failure propagates to handle_root_error now call bun_core::getcwd instead of bun_sys::getcwd: the --cwd base and the default cwd in Arguments::parse (CLI commands), and FileSystem::init in the resolver (compiled binaries). Every other bun_sys::getcwd caller is unchanged, and bun_sys::Error itself is untouched.
  • Verified by test/cli/run/run-crash-handler.test.ts (bun install and bun test from a deleted cwd print the hint; bun -e boots via the exe-dir fallback) and the existing compiled-binary case in test/bundler/bun-build-compile.test.ts, which now asserts the hint. With the four src/ files reverted to the merge base, the three hint assertions fail (generic ENOENT message); with them applied, all pass.

Background

  • handle_root_error is where an Err returned from bun's main ends up. It takes anything implementing ErrName and switches on the error's name string to pick a user-facing message, falling back to a generic one.
  • Each crate has its own thiserror enum with a Core(bun_core::Error) variant, and name() on a wrapper delegates to the inner error, so a bun_core variant returned at the bottom of the chain (bun_core::getcwd -> resolver -> bundler -> jsc -> runtime) surfaces its name unchanged at the top.
  • bun_core::getcwd and bun_sys::getcwd are two wrappers over the same libc call; the difference is the error type. bun_sys's returns the rich syscall error, which other callers want for JS-visible errors; bun_core's returns a bun_core::Error, which is what the startup paths need here.
Earlier shape of this PR

The first version added a bun_sys::Error::to_named_core_err() helper holding the (getcwd, ENOENT) mapping and consulted it from the From<bun_sys::Error> impls in bun_runtime and bun_resolver. 4c99108 replaced that with switching the three call sites to bun_core::getcwd, which keeps the mapping in one place and leaves bun_sys and the From impls as they were.