Repository navigation
Conversation
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.
|
Updated 1:05 AM PT - Nov 11th, 2026
❌ @robobun, your commit de58ed4 has some failures in 🧪 To try this PR locally: bunx bun-pr 48560That installs a local version of the PR into your bun-48560 --bun |
|
Status: ready for review. The diff is green. The red CI status is not a test failure. Reproduced on 1.4.3-canary (367d939) and confirmed in the source of main at 89452d2. This script prints try {
await Bun.file(987654).delete();
console.log("the try saw nothing");
} catch (e) {
console.log("the try caught " + e.code);
}An open descriptor, CI at de58ed4 (build 140072): all 184 jobs that ran passed. That includes the Windows 2019 x64 and Windows 11 aarch64 test lanes, and both ran |
|
No actionable comments were generated in the recent review. WalkthroughFile-descriptor inputs to ChangesFile descriptor unlink behavior
Possibly related PRs
Suggested reviewers: Merge Risk:
|
There was a problem hiding this comment.
Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.
LGTM — one-line fix that swaps the fd arm of FileExt::unlink from a resolved promise to the documented rejected_promise(...).to_js() idiom, with a test covering both the unlink and delete aliases; the inline nit about pinning the error class is non-blocking.
I did not run the new test; that it fails on the base branch follows from reading (the old arm resolved, so .rejects cannot pass there).
Extended reasoning...
The diff touches src/runtime/webcore/blob/Store.rs (one match arm in FileExt::unlink for PathOrFileDescriptor::Fd) and adds a test.each case to test/js/web/fetch/blob-write.test.ts; it touches no security-sensitive surface. JSPromise::rejected_promise returns a GC-managed &mut JSPromise whose doc comment names .to_js() as the conversion, and the C++ JSC__JSPromise__rejectedPromise already handles an empty value from a terminated error build, so no exception check is skipped relative to the old path. delete is declared in Blob.bind.ts as an alias with rustName: "unlink", so both user-facing entry points go through the fixed arm, and a repo-wide grep found no existing test or doc asserting the old resolve-with-error behavior. The only behavioral downside (callers who never await now get an unhandled rejection) is the intended correction of a promise that resolved with an error object, which the PR description states. Neither file is covered by .github/CODEOWNERS, the change is small and self-contained, and the single inline finding is a minor test-strength nit.
|
Applied the optional review suggestion in de58ed4: the new test also pins the error class with |
Problem
Bun.file(fd).delete()and.unlink()resolve, and the resolved value is the error:TypeError [ERR_INVALID_ARG_TYPE]: Is not possible to unlink a file descriptor. Anawaitinsidetry/catchsees no error.Fdarm ofFileExt::unlink(src/runtime/webcore/blob/Store.rs:233). It builds the error and passes it toJSPromise::resolved_promise_value.Fix
JSPromise::rejected_promise(...). The error, its code and its message do not change.ENOENT), andfs.promises.unlink(fd)rejects with the same code.test/js/web/fetch/blob-write.test.ts, two new cases. Without the fix both fail withExpected promise that rejects, Received promise that resolved. Also rantest/js/bun/util/bun-file.test.ts.Background
BunFilestore holds a path or a file descriptor. A descriptor has no path, sodelete()has nothing to unlink and reports an argument error.resolved_promise_valueinsrc/, only this one passes an error.Blobdoes fordelete(). This arm already returned a promise, and the path form reports its errors through the promise.Downsides
Bun.file(fd).delete()and does not handle the result got a resolved promise before. It now gets a rejection. With no handler, the process prints the error and exits with code 1, as the path form does for a missing file.if (result instanceof Error)) no longer reaches that branch.Notes
Reproduction, before the fix prints
the try saw nothing, after it printsthe try caught ERR_INVALID_ARG_TYPE:The descriptor does not have to be invalid. An open descriptor and
Bun.stdin.delete()take the same arm. The file is not deleted in any of these cases, before or after the fix.The line dates from #15740, which added
unlink(). In the same hunk the error arm of the path form usedrejectedPromiseValue, and thefdarm usedresolvedPromiseValue. The behaviour was never correct, so the test is in the existing file for this API and not undertest/regression/.Not changed:
new Blob(["x"]).delete()throws synchronously (Cannot write to a Blob backed by bytes, which are always read-only). Two existing tests inblob-write.test.tspin that.Cost for callers that never hit this arm: none. The change replaces one call in an arm that runs only for
delete()orunlink()on a descriptor-backed file.