From 677a2c259ca50862cbbd4a9cc95c64d810fbcccd Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Wed, 11 Nov 2026 07:37:54 +0000 Subject: [PATCH 1/2] Bun.file(fd).delete(): reject instead of resolving with the TypeError The Fd arm of FileExt::unlink built ERR_INVALID_ARG_TYPE and passed it to JSPromise::resolved_promise_value, so delete() and unlink() on a file made from a descriptor resolved with the error as their value. The arm now returns a rejected promise with the same error. --- src/runtime/webcore/blob/Store.rs | 5 +++-- test/js/web/fetch/blob-write.test.ts | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/runtime/webcore/blob/Store.rs b/src/runtime/webcore/blob/Store.rs index 15130252fdbc..d3a7ed83c97b 100644 --- a/src/runtime/webcore/blob/Store.rs +++ b/src/runtime/webcore/blob/Store.rs @@ -230,7 +230,7 @@ impl FileExt for File { None, )) } - PathOrFileDescriptor::Fd(_) => Ok(JSPromise::resolved_promise_value( + PathOrFileDescriptor::Fd(_) => Ok(JSPromise::rejected_promise( cx.global(), // `JSGlobalObject::create_invalid_args` lives in the still-gated // `JSGlobalObject.rs`; `ERR_INVALID_ARG_TYPE` (lib.rs) is the @@ -238,7 +238,8 @@ impl FileExt for File { cx.global().ERR_INVALID_ARG_TYPE(format_args!( "Is not possible to unlink a file descriptor" )), - )), + ) + .to_js()), } } } diff --git a/test/js/web/fetch/blob-write.test.ts b/test/js/web/fetch/blob-write.test.ts index 02d3111eb98b..be10c11fc397 100644 --- a/test/js/web/fetch/blob-write.test.ts +++ b/test/js/web/fetch/blob-write.test.ts @@ -1,4 +1,5 @@ import { expect, test } from "bun:test"; +import { closeSync, openSync } from "fs"; import { expectMaxObjectTypeCount, tempDir, tempDirWithFiles } from "harness"; import path from "path"; @@ -55,6 +56,21 @@ test("Bun.file(path).delete() does not throw", async () => { expect(await Bun.file(path.join(dir, "a")).exists()).toBe(false); }); +// A file made from a descriptor has no path to unlink. +test.each(["unlink", "delete"] as const)("Bun.file(fd).%s() rejects", async method => { + await using dir = tempDir("bun-unlink-fd", { a: "Hello, world!" }); + const fd = openSync(path.join(dir, "a"), "r"); + try { + await expect(Bun.file(fd)[method]()).rejects.toMatchObject({ + code: "ERR_INVALID_ARG_TYPE", + message: "Is not possible to unlink a file descriptor", + }); + } finally { + closeSync(fd); + } + expect(await Bun.file(path.join(dir, "a")).exists()).toBe(true); +}); + test("blob.writer() throws for data-backed blob", () => { const blob = new Blob(["Hello, world!"]) as any; expect(() => blob.writer()).toThrowErrorMatchingInlineSnapshot( From de58ed4ea3bd926e1aca0bc849fbf55994418c86 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Wed, 11 Nov 2026 07:52:11 +0000 Subject: [PATCH 2/2] test: pin the error class of the Bun.file(fd).delete() rejection --- test/js/web/fetch/blob-write.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/js/web/fetch/blob-write.test.ts b/test/js/web/fetch/blob-write.test.ts index be10c11fc397..2a45f53546af 100644 --- a/test/js/web/fetch/blob-write.test.ts +++ b/test/js/web/fetch/blob-write.test.ts @@ -61,7 +61,9 @@ test.each(["unlink", "delete"] as const)("Bun.file(fd).%s() rejects", async meth await using dir = tempDir("bun-unlink-fd", { a: "Hello, world!" }); const fd = openSync(path.join(dir, "a"), "r"); try { - await expect(Bun.file(fd)[method]()).rejects.toMatchObject({ + const promise = Bun.file(fd)[method](); + await expect(promise).rejects.toBeInstanceOf(TypeError); + await expect(promise).rejects.toMatchObject({ code: "ERR_INVALID_ARG_TYPE", message: "Is not possible to unlink a file descriptor", });