From 374aa32b2a367f9cb32539d8c5fbd7def31b472c Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Mon, 27 Apr 2026 14:55:06 +0000 Subject: [PATCH 1/4] fix(ext/node): wrap process.chdir errors with path/dest/syscall Node's `process.chdir` raises a `uv_chdir`-style Error with `code: 'ENOENT'`, `syscall: 'chdir'`, `path` (the cwd before the call), and `dest` (the target). Deno was passing through Deno's `NotFound` error verbatim, so consumers saw `name: 'NotFound'`, no `path`, no `dest`, and a non-uv-shaped message. Snapshot the cwd before the call and run the result through `denoErrorToNodeError` with the expected uv context. Enables `parallel/test-process-chdir-errormessage.js`. --- ext/node/polyfills/_process/process.ts | 15 ++++++++++++++- tests/node_compat/config.jsonc | 1 + 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/ext/node/polyfills/_process/process.ts b/ext/node/polyfills/_process/process.ts index 9aa9227094071f..b849bb1478e603 100644 --- a/ext/node/polyfills/_process/process.ts +++ b/ext/node/polyfills/_process/process.ts @@ -26,6 +26,7 @@ import { nextTick as _nextTick } from "ext:deno_node/_next_tick.ts"; import { _exiting } from "ext:deno_node/_process/exiting.ts"; import * as fs from "ext:deno_fs/30_fs.js"; import { + denoErrorToNodeError, ERR_INVALID_ARG_TYPE, ERR_INVALID_OBJECT_DEFINE_PROPERTY, } from "ext:deno_node/internal/errors.ts"; @@ -51,7 +52,19 @@ export function chdir(directory: string): void { if (typeof directory !== "string") { throw new ERR_INVALID_ARG_TYPE("directory", "string", directory); } - fs.chdir(directory); + // Node's chdir error carries `path` (the cwd before chdir), `dest` (the + // target), and `syscall: 'chdir'`. Snapshot the cwd before attempting the + // change so the error's `path` matches Node's behaviour. + const fromPath = fs.cwd(); + try { + fs.chdir(directory); + } catch (err) { + throw denoErrorToNodeError(err as Error, { + syscall: "chdir", + path: fromPath, + dest: directory, + }); + } } /** https://nodejs.org/api/process.html#process_process_cwd */ diff --git a/tests/node_compat/config.jsonc b/tests/node_compat/config.jsonc index d5b167d7041942..65003c2ac9a18e 100644 --- a/tests/node_compat/config.jsonc +++ b/tests/node_compat/config.jsonc @@ -2430,6 +2430,7 @@ "parallel/test-process-beforeexit-throw-exit.js": {}, "parallel/test-process-binding-internalbinding-allowlist.js": {}, "parallel/test-process-binding.js": {}, + "parallel/test-process-chdir-errormessage.js": {}, "parallel/test-process-chdir.js": {}, "parallel/test-process-config.js": {}, "parallel/test-process-constants-noatime.js": { From 7677cb0ddc58be77033fd547f9bfc4b899952966 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Mon, 27 Apr 2026 17:00:26 +0000 Subject: [PATCH 2/4] fix(ext/node): tolerate deleted cwd in process.chdir snapshot When the current working directory has been deleted (common in tmpdir cleanup during process exit), fs.cwd() throws NotFound. The new process.chdir wrapper from this PR was reading cwd unconditionally before the chdir attempt, propagating that throw as an uncaught NotFound and breaking test-fs-mkdir.js (which exercises the tmpdir cleanup path). Wrap the cwd snapshot in try/catch and fall back to an empty path so the chdir attempt still runs and any failure is shaped through denoErrorToNodeError as before. --- ext/node/polyfills/_process/process.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/ext/node/polyfills/_process/process.ts b/ext/node/polyfills/_process/process.ts index b849bb1478e603..0c7a398f9ee63e 100644 --- a/ext/node/polyfills/_process/process.ts +++ b/ext/node/polyfills/_process/process.ts @@ -54,8 +54,16 @@ export function chdir(directory: string): void { } // Node's chdir error carries `path` (the cwd before chdir), `dest` (the // target), and `syscall: 'chdir'`. Snapshot the cwd before attempting the - // change so the error's `path` matches Node's behaviour. - const fromPath = fs.cwd(); + // change so the error's `path` matches Node's behaviour. If the current + // cwd has been deleted (common in tmpdir cleanup during process exit), + // `fs.cwd()` itself throws — fall back to an empty string so the wrapper + // still has a sensible `path`, and don't surface the cwd lookup error. + let fromPath = ""; + try { + fromPath = fs.cwd(); + } catch { + // Ignore — chdir() below will surface a chdir-shaped error. + } try { fs.chdir(directory); } catch (err) { From 719097d4567899c2a87caa71a6f0d6ca783b18d4 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Mon, 27 Apr 2026 17:08:43 +0000 Subject: [PATCH 3/4] chore: replace U+2014 em dash with ASCII -- in process.ts The snapshot build at libs/core/extensions.rs:139 enforces 7-bit ASCII on ext:deno_node/* files. The comments I added in 7677cb0dd used em dashes which broke the snapshot build on linux-aarch64. --- ext/node/polyfills/_process/process.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/node/polyfills/_process/process.ts b/ext/node/polyfills/_process/process.ts index 0c7a398f9ee63e..5acbd26d28a05e 100644 --- a/ext/node/polyfills/_process/process.ts +++ b/ext/node/polyfills/_process/process.ts @@ -56,13 +56,13 @@ export function chdir(directory: string): void { // target), and `syscall: 'chdir'`. Snapshot the cwd before attempting the // change so the error's `path` matches Node's behaviour. If the current // cwd has been deleted (common in tmpdir cleanup during process exit), - // `fs.cwd()` itself throws — fall back to an empty string so the wrapper + // `fs.cwd()` itself throws -- fall back to an empty string so the wrapper // still has a sensible `path`, and don't surface the cwd lookup error. let fromPath = ""; try { fromPath = fs.cwd(); } catch { - // Ignore — chdir() below will surface a chdir-shaped error. + // Ignore -- chdir() below will surface a chdir-shaped error. } try { fs.chdir(directory); From d9fb84c0ba78b83f8718d18c3894649770e322b5 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Mon, 27 Apr 2026 17:15:41 +0000 Subject: [PATCH 4/4] fix unit_node process.chdir failure test for new error shape The wrapper now emits a Node-shaped uv error (Error with code, syscall, path, dest) instead of Deno.errors.NotFound. Update the existing 'process.chdir failure' test to assert that shape. --- tests/unit_node/process_test.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tests/unit_node/process_test.ts b/tests/unit_node/process_test.ts index 7f0c6d575ebc4c..5a017fa29b7f83 100644 --- a/tests/unit_node/process_test.ts +++ b/tests/unit_node/process_test.ts @@ -76,16 +76,19 @@ Deno.test({ Deno.test({ name: "process.chdir failure", fn() { - assertThrows( + // process.chdir now wraps Deno errors into Node-shaped uv errors with + // syscall/path/dest, so a missing directory throws a plain Error with + // code "ENOENT" rather than Deno.errors.NotFound. + const err = assertThrows( () => { process.chdir("non-existent-directory-name"); }, - Deno.errors.NotFound, - "file", - // On every OS Deno returns: "No such file" except for Windows, where it's: - // "The system cannot find the file specified. (os error 2)" so "file" is - // the only common string here. - ); + Error, + "ENOENT", + ) as Error & { code?: string; syscall?: string; dest?: string }; + assertEquals(err.code, "ENOENT"); + assertEquals(err.syscall, "chdir"); + assertEquals(err.dest, "non-existent-directory-name"); }, });