pi-mono/packages/coding-agent/test/deleted-cwd.test.ts
robopipi[bot] 88ffcdc734
fix(coding-agent): report a deleted working directory instead of crashing on startup (#11511)
Starting `pi` from a directory that has been deleted crashed with an
uncaught Node `ENOENT ... uv_cwd` stack trace. This also happened for
`--version` and `--help`. The cause is the unguarded `process.cwd()`
call in `main()` (`packages/coding-agent/src/main.ts`).

`main()` now catches `ENOENT` from that call, prints one line telling
the user to change to an existing directory, and exits with code 1.
Other errors are rethrown unchanged. Argument handling is otherwise
unchanged: `--version` still needs a valid cwd, but it now gets the
readable error instead of a crash.

**Testing**
- Added `test/deleted-cwd.test.ts`. It stubs `process.cwd()` to throw
ENOENT and checks that `main()` prints the message and exits with code
1. It also checks that a non-ENOENT error is rethrown. The first test
fails without the change and passes with it.
- `npm run check` passes.
- Manual check with the built CLI. Before, all three commands below
printed the `uv_cwd` stack trace. After, each prints `Error: The current
working directory no longer exists. Change to an existing directory and
try again.` and exits 1:
  ```sh
  d=$(mktemp -d); cd "$d"; rmdir "$d"
  pi -ne --version; pi -ne --help; pi -ne
  ```

Fixes #11509

Requested by @badlogic

Co-authored-by: triagent <triagent@localhost>
2026-10-28 09:51:39 +01:00

35 lines
1.3 KiB
TypeScript

import { afterEach, describe, expect, test, vi } from "vitest";
import { main } from "../src/main.ts";
// Regression test for https://github.com/earendil-works/pi/issues/11509
describe("startup in a deleted working directory", () => {
afterEach(() => {
vi.restoreAllMocks();
});
test("prints an actionable error and exits 1 instead of throwing", async () => {
vi.spyOn(process, "cwd").mockImplementation(() => {
throw Object.assign(new Error("ENOENT: no such file or directory, uv_cwd"), {
code: "ENOENT",
syscall: "uv_cwd",
});
});
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
const exitSpy = vi.spyOn(process, "exit").mockImplementation(((code?: number) => {
throw new Error(`process.exit(${code})`);
}) as typeof process.exit);
await expect(main(["--no-extensions", "--version"])).rejects.toThrow("process.exit(1)");
expect(exitSpy).toHaveBeenCalledWith(1);
const stderr = errorSpy.mock.calls.map(([message]) => String(message)).join("\n");
expect(stderr).toContain("The current working directory no longer exists");
});
test("rethrows unrelated cwd errors", async () => {
vi.spyOn(process, "cwd").mockImplementation(() => {
throw Object.assign(new Error("EACCES"), { code: "EACCES" });
});
await expect(main(["--no-extensions", "--version"])).rejects.toThrow("EACCES");
});
});