Version
v26.7.0 (reproduced; code path unchanged on main)
Platform
macOS (Darwin 25.5.0, arm64) — not platform-specific
Subsystem
fs
What steps will reproduce the bug?
const { openAsBlob } = require("node:fs");
try {
openAsBlob("./does-not-exist.txt");
} catch (error) {
console.log(error.name, error.code, error.message);
// TypeError ERR_INVALID_ARG_VALUE Unable to open file as blob
}
Always reproduces. Same result for a permission failure — every open/stat failure produces the identical error.
What is the expected behavior? Why is that the expected behavior?
A rejection (or at least an error) equivalent to what every other fs open/stat API produces for a missing file: a system error with code: "ENOENT", plus syscall and path. ERR_INVALID_ARG_VALUE (a TypeError) says the argument is malformed, but the path here is a perfectly valid argument — the file just doesn't exist. The docs for openAsBlob don't document any different error contract.
What do you see instead?
TypeError [ERR_INVALID_ARG_VALUE]: Unable to open file as blob — thrown synchronously (the synchronous-throw half is already tracked in #62418), with no errno, syscall, or path. ENOENT, EACCES, and any other underlying failure are indistinguishable.
Additional information
Mechanism:
lib/fs.js openAsBlob() calls createBlobFromFilePath(path) synchronously and wraps the result in PromiseResolve.
src/node_blob.cc BlobFromFilePath() calls DataQueue::CreateFdEntry(env, args[0]) and, when it gets nullptr, throws THROW_ERR_INVALID_ARG_VALUE(env, "Unable to open file as blob").
src/dataqueue/queue.cc FdEntry::Create() returns nullptr for any negative uv_fs_stat/open result, discarding the libuv status code — this is where the ENOENT is lost.
Note that the pending fix for #62418 (PR #62655) only converts the synchronous throw into a promise rejection; its new test asserts code: 'ERR_INVALID_ARG_VALUE' for openAsBlob('does-not-exist'), which would enshrine the misleading code. It would be good to fix the error identity at the same time: have the native side (or FdEntry::Create) propagate the uv status and throw a proper UVException (ENOENT/EACCES/… with syscall/path), as fs.open does.
Real-world impact: the generic ERR_INVALID_ARG_VALUE sent us debugging "path validation" changes for a test-suite failure whose actual cause was simply a cwd-relative path to a file that didn't exist — an ENOENT with the path attached would have made it a ten-second diagnosis.
Related observation while testing (can file separately if preferred): openAsBlob("./some-directory") succeeds and returns a Blob sized to the directory's stat size; reading it later fails with NotReadableError: The blob could not be read. An EISDIR at open time would be more helpful there too.
Version
v26.7.0 (reproduced; code path unchanged on
main)Platform
macOS (Darwin 25.5.0, arm64) — not platform-specific
Subsystem
fsWhat steps will reproduce the bug?
Always reproduces. Same result for a permission failure — every open/stat failure produces the identical error.
What is the expected behavior? Why is that the expected behavior?
A rejection (or at least an error) equivalent to what every other
fsopen/stat API produces for a missing file: a system error withcode: "ENOENT", plussyscallandpath.ERR_INVALID_ARG_VALUE(aTypeError) says the argument is malformed, but the path here is a perfectly valid argument — the file just doesn't exist. The docs foropenAsBlobdon't document any different error contract.What do you see instead?
TypeError [ERR_INVALID_ARG_VALUE]: Unable to open file as blob— thrown synchronously (the synchronous-throw half is already tracked in #62418), with noerrno,syscall, orpath. ENOENT, EACCES, and any other underlying failure are indistinguishable.Additional information
Mechanism:
lib/fs.jsopenAsBlob()callscreateBlobFromFilePath(path)synchronously and wraps the result inPromiseResolve.src/node_blob.ccBlobFromFilePath()callsDataQueue::CreateFdEntry(env, args[0])and, when it getsnullptr, throwsTHROW_ERR_INVALID_ARG_VALUE(env, "Unable to open file as blob").src/dataqueue/queue.ccFdEntry::Create()returnsnullptrfor any negativeuv_fs_stat/open result, discarding the libuv status code — this is where the ENOENT is lost.Note that the pending fix for #62418 (PR #62655) only converts the synchronous throw into a promise rejection; its new test asserts
code: 'ERR_INVALID_ARG_VALUE'foropenAsBlob('does-not-exist'), which would enshrine the misleading code. It would be good to fix the error identity at the same time: have the native side (orFdEntry::Create) propagate the uv status and throw a properUVException(ENOENT/EACCES/… withsyscall/path), asfs.opendoes.Real-world impact: the generic
ERR_INVALID_ARG_VALUEsent us debugging "path validation" changes for a test-suite failure whose actual cause was simply a cwd-relative path to a file that didn't exist — anENOENTwith the path attached would have made it a ten-second diagnosis.Related observation while testing (can file separately if preferred):
openAsBlob("./some-directory")succeeds and returns aBlobsized to the directory's stat size; reading it later fails withNotReadableError: The blob could not be read. An EISDIR at open time would be more helpful there too.