Skip to content

fs: fix recursive readdir with buffer encoding - #64954

Open
canblmz1 wants to merge 1 commit into
nodejs:mainfrom
canblmz1:fix/readdir-recursive-buffer-path
Open

fs: fix recursive readdir with buffer encoding#64954
canblmz1 wants to merge 1 commit into
nodejs:mainfrom
canblmz1:fix/readdir-recursive-buffer-path

Conversation

@canblmz1

Copy link
Copy Markdown

Description

fs.readdir(), fs.readdirSync(), and fs.promises.readdir() throw
ERR_INVALID_ARG_TYPE (or crash the process outright in the callback
case) when called with both { recursive: true } and
{ encoding: 'buffer' }. The internal recursive walk builds paths with
path.join()/path.relative(), and checks whether an entry is a
directory via a CommonJS-module-resolution-specific native stat
binding (internalModuleStat) - none of these accept Buffer
arguments, which is what entry names become once encoding: 'buffer'
is set.

constfs=require('fs');fs.readdirSync('.',{recursive: true,encoding: 'buffer'});// Uncaught TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument// must be of type string. Received an instance of Buffer

Fix

  • internal/fs/utils already had an unexported join() helper that
    correctly handles string/Buffer combinations; this exports it and
    adds two more helpers alongside it: relativeToBasePath() (avoids
    needing path.relative()'s Buffer support, since the full path is
    always built by repeatedly joining onto the base path) and
    isDirectoryPath().
  • isDirectoryPath() keeps using internalModuleStat for the string
    fast path (unchanged behavior/performance for the common case), but
    falls back to the general-purpose stat binding - the same one
    fs.statSync() uses - for Buffer paths. That binding handles
    Buffers correctly at the native layer, so this also avoids a lossy
    string round-trip for non-UTF8 file names, rather than just papering
    over the reported crash.
  • lib/fs.js (handleDirents/handleFilePaths) and
    lib/internal/fs/promises.js (readdirRecursive) both had their
    own copy of this bug and are updated to use the shared helpers.

Testing

Added test/parallel/test-fs-readdir-recursive-buffer.js, covering
readdirSync, readdirSync with withFileTypes, the readdir
callback form, and fs.promises.readdir, all with
{ recursive: true, encoding: 'buffer' }.

Ran the full test/parallel/test-fs-readdir*.js suite plus a broader
test/parallel/test-fs-*.js sweep (257 files) locally on Windows
(clang-cl build) - all pass except 7 pre-existing failures unrelated
to this change (EPERM on symlinkSync due to this environment not
running elevated/without Developer Mode, verified individually).

Fixes: #58892

`fs.readdir()`, `fs.readdirSync()`, and `fs.promises.readdir()` threw
ERR_INVALID_ARG_TYPE (or crashed the process outright in the callback
case) when called with both `{ recursive: true }` and
`{ encoding: 'buffer' }`, because the internal recursive walk used
`path.join()`, `path.relative()`, and a CommonJS-module-resolution
specific native stat binding, none of which accept Buffer arguments.
Adds `relativeToBasePath()` and `isDirectoryPath()` helpers to
`internal/fs/utils`, alongside the existing (but previously
unexported) `join()` helper, all of which handle both string and
Buffer paths. The recursive readdir implementations in `lib/fs.js`
and `lib/internal/fs/promises.js` now use these instead of calling
`path`/the module-resolution stat binding directly.
`isDirectoryPath()` falls back to the general-purpose `stat` binding
(the same one `fs.statSync()` uses) for Buffer paths, so it also
handles non-UTF8 file names correctly instead of round-tripping
through a lossy string conversion.
Fixes: nodejs#58892
Signed-off-by: Can <hello@syntaxandco.com>
@nodejs-github-botnodejs-github-bot added fs Issues and PRs related to file-system APIs and the fs module. needs-ci PRs that need a full CI run. labels Aug 2, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fsIssues and PRs related to file-system APIs and the fs module.needs-ciPRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

all versions of readdir don't work in recursive mode when used with a buffer argument

2 participants

@canblmz1@nodejs-github-bot
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
fs: fix recursive readdir with buffer encoding by canblmz1 · Pull Request #64954 · nodejs/node · GitHub
Skip to content

fs: fix recursive readdir with buffer encoding - #64954

Open
canblmz1 wants to merge 1 commit into
nodejs:mainfrom
canblmz1:fix/readdir-recursive-buffer-path
Open

fs: fix recursive readdir with buffer encoding#64954
canblmz1 wants to merge 1 commit into
nodejs:mainfrom
canblmz1:fix/readdir-recursive-buffer-path

Conversation

@canblmz1

Copy link
Copy Markdown

Description

fs.readdir(), fs.readdirSync(), and fs.promises.readdir() throw
ERR_INVALID_ARG_TYPE (or crash the process outright in the callback
case) when called with both { recursive: true } and
{ encoding: 'buffer' }. The internal recursive walk builds paths with
path.join()/path.relative(), and checks whether an entry is a
directory via a CommonJS-module-resolution-specific native stat
binding (internalModuleStat) - none of these accept Buffer
arguments, which is what entry names become once encoding: 'buffer'
is set.

constfs=require('fs');fs.readdirSync('.',{recursive: true,encoding: 'buffer'});// Uncaught TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument// must be of type string. Received an instance of Buffer

Fix

  • internal/fs/utils already had an unexported join() helper that
    correctly handles string/Buffer combinations; this exports it and
    adds two more helpers alongside it: relativeToBasePath() (avoids
    needing path.relative()'s Buffer support, since the full path is
    always built by repeatedly joining onto the base path) and
    isDirectoryPath().
  • isDirectoryPath() keeps using internalModuleStat for the string
    fast path (unchanged behavior/performance for the common case), but
    falls back to the general-purpose stat binding - the same one
    fs.statSync() uses - for Buffer paths. That binding handles
    Buffers correctly at the native layer, so this also avoids a lossy
    string round-trip for non-UTF8 file names, rather than just papering
    over the reported crash.
  • lib/fs.js (handleDirents/handleFilePaths) and
    lib/internal/fs/promises.js (readdirRecursive) both had their
    own copy of this bug and are updated to use the shared helpers.

Testing

Added test/parallel/test-fs-readdir-recursive-buffer.js, covering
readdirSync, readdirSync with withFileTypes, the readdir
callback form, and fs.promises.readdir, all with
{ recursive: true, encoding: 'buffer' }.

Ran the full test/parallel/test-fs-readdir*.js suite plus a broader
test/parallel/test-fs-*.js sweep (257 files) locally on Windows
(clang-cl build) - all pass except 7 pre-existing failures unrelated
to this change (EPERM on symlinkSync due to this environment not
running elevated/without Developer Mode, verified individually).

Fixes: #58892

`fs.readdir()`, `fs.readdirSync()`, and `fs.promises.readdir()` threw
ERR_INVALID_ARG_TYPE (or crashed the process outright in the callback
case) when called with both `{ recursive: true }` and
`{ encoding: 'buffer' }`, because the internal recursive walk used
`path.join()`, `path.relative()`, and a CommonJS-module-resolution
specific native stat binding, none of which accept Buffer arguments.
Adds `relativeToBasePath()` and `isDirectoryPath()` helpers to
`internal/fs/utils`, alongside the existing (but previously
unexported) `join()` helper, all of which handle both string and
Buffer paths. The recursive readdir implementations in `lib/fs.js`
and `lib/internal/fs/promises.js` now use these instead of calling
`path`/the module-resolution stat binding directly.
`isDirectoryPath()` falls back to the general-purpose `stat` binding
(the same one `fs.statSync()` uses) for Buffer paths, so it also
handles non-UTF8 file names correctly instead of round-tripping
through a lossy string conversion.
Fixes: nodejs#58892
Signed-off-by: Can <hello@syntaxandco.com>
@nodejs-github-botnodejs-github-bot added fs Issues and PRs related to file-system APIs and the fs module. needs-ci PRs that need a full CI run. labels Aug 2, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fsIssues and PRs related to file-system APIs and the fs module.needs-ciPRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

all versions of readdir don't work in recursive mode when used with a buffer argument

2 participants

@canblmz1@nodejs-github-bot
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fs: fix recursive readdir with buffer encoding by canblmz1 · Pull Request #64954 · nodejs/node · GitHub
Skip to content

fs: fix recursive readdir with buffer encoding - #64954

Open
canblmz1 wants to merge 1 commit into
nodejs:mainfrom
canblmz1:fix/readdir-recursive-buffer-path
Open

fs: fix recursive readdir with buffer encoding#64954
canblmz1 wants to merge 1 commit into
nodejs:mainfrom
canblmz1:fix/readdir-recursive-buffer-path

Conversation

@canblmz1

Copy link
Copy Markdown

Description

fs.readdir(), fs.readdirSync(), and fs.promises.readdir() throw
ERR_INVALID_ARG_TYPE (or crash the process outright in the callback
case) when called with both { recursive: true } and
{ encoding: 'buffer' }. The internal recursive walk builds paths with
path.join()/path.relative(), and checks whether an entry is a
directory via a CommonJS-module-resolution-specific native stat
binding (internalModuleStat) - none of these accept Buffer
arguments, which is what entry names become once encoding: 'buffer'
is set.

constfs=require('fs');fs.readdirSync('.',{recursive: true,encoding: 'buffer'});// Uncaught TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument// must be of type string. Received an instance of Buffer

Fix

  • internal/fs/utils already had an unexported join() helper that
    correctly handles string/Buffer combinations; this exports it and
    adds two more helpers alongside it: relativeToBasePath() (avoids
    needing path.relative()'s Buffer support, since the full path is
    always built by repeatedly joining onto the base path) and
    isDirectoryPath().
  • isDirectoryPath() keeps using internalModuleStat for the string
    fast path (unchanged behavior/performance for the common case), but
    falls back to the general-purpose stat binding - the same one
    fs.statSync() uses - for Buffer paths. That binding handles
    Buffers correctly at the native layer, so this also avoids a lossy
    string round-trip for non-UTF8 file names, rather than just papering
    over the reported crash.
  • lib/fs.js (handleDirents/handleFilePaths) and
    lib/internal/fs/promises.js (readdirRecursive) both had their
    own copy of this bug and are updated to use the shared helpers.

Testing

Added test/parallel/test-fs-readdir-recursive-buffer.js, covering
readdirSync, readdirSync with withFileTypes, the readdir
callback form, and fs.promises.readdir, all with
{ recursive: true, encoding: 'buffer' }.

Ran the full test/parallel/test-fs-readdir*.js suite plus a broader
test/parallel/test-fs-*.js sweep (257 files) locally on Windows
(clang-cl build) - all pass except 7 pre-existing failures unrelated
to this change (EPERM on symlinkSync due to this environment not
running elevated/without Developer Mode, verified individually).

Fixes: #58892

`fs.readdir()`, `fs.readdirSync()`, and `fs.promises.readdir()` threw
ERR_INVALID_ARG_TYPE (or crashed the process outright in the callback
case) when called with both `{ recursive: true }` and
`{ encoding: 'buffer' }`, because the internal recursive walk used
`path.join()`, `path.relative()`, and a CommonJS-module-resolution
specific native stat binding, none of which accept Buffer arguments.
Adds `relativeToBasePath()` and `isDirectoryPath()` helpers to
`internal/fs/utils`, alongside the existing (but previously
unexported) `join()` helper, all of which handle both string and
Buffer paths. The recursive readdir implementations in `lib/fs.js`
and `lib/internal/fs/promises.js` now use these instead of calling
`path`/the module-resolution stat binding directly.
`isDirectoryPath()` falls back to the general-purpose `stat` binding
(the same one `fs.statSync()` uses) for Buffer paths, so it also
handles non-UTF8 file names correctly instead of round-tripping
through a lossy string conversion.
Fixes: nodejs#58892
Signed-off-by: Can <hello@syntaxandco.com>
@nodejs-github-botnodejs-github-bot added fs Issues and PRs related to file-system APIs and the fs module. needs-ci PRs that need a full CI run. labels Aug 2, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fsIssues and PRs related to file-system APIs and the fs module.needs-ciPRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

all versions of readdir don't work in recursive mode when used with a buffer argument

2 participants

@canblmz1@nodejs-github-bot
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fs: fix recursive readdir with buffer encoding by canblmz1 · Pull Request #64954 · nodejs/node · GitHub
Skip to content

fs: fix recursive readdir with buffer encoding - #64954

Open
canblmz1 wants to merge 1 commit into
nodejs:mainfrom
canblmz1:fix/readdir-recursive-buffer-path
Open

fs: fix recursive readdir with buffer encoding#64954
canblmz1 wants to merge 1 commit into
nodejs:mainfrom
canblmz1:fix/readdir-recursive-buffer-path

Conversation

@canblmz1

Copy link
Copy Markdown

Description

fs.readdir(), fs.readdirSync(), and fs.promises.readdir() throw
ERR_INVALID_ARG_TYPE (or crash the process outright in the callback
case) when called with both { recursive: true } and
{ encoding: 'buffer' }. The internal recursive walk builds paths with
path.join()/path.relative(), and checks whether an entry is a
directory via a CommonJS-module-resolution-specific native stat
binding (internalModuleStat) - none of these accept Buffer
arguments, which is what entry names become once encoding: 'buffer'
is set.

constfs=require('fs');fs.readdirSync('.',{recursive: true,encoding: 'buffer'});// Uncaught TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument// must be of type string. Received an instance of Buffer

Fix

  • internal/fs/utils already had an unexported join() helper that
    correctly handles string/Buffer combinations; this exports it and
    adds two more helpers alongside it: relativeToBasePath() (avoids
    needing path.relative()'s Buffer support, since the full path is
    always built by repeatedly joining onto the base path) and
    isDirectoryPath().
  • isDirectoryPath() keeps using internalModuleStat for the string
    fast path (unchanged behavior/performance for the common case), but
    falls back to the general-purpose stat binding - the same one
    fs.statSync() uses - for Buffer paths. That binding handles
    Buffers correctly at the native layer, so this also avoids a lossy
    string round-trip for non-UTF8 file names, rather than just papering
    over the reported crash.
  • lib/fs.js (handleDirents/handleFilePaths) and
    lib/internal/fs/promises.js (readdirRecursive) both had their
    own copy of this bug and are updated to use the shared helpers.

Testing

Added test/parallel/test-fs-readdir-recursive-buffer.js, covering
readdirSync, readdirSync with withFileTypes, the readdir
callback form, and fs.promises.readdir, all with
{ recursive: true, encoding: 'buffer' }.

Ran the full test/parallel/test-fs-readdir*.js suite plus a broader
test/parallel/test-fs-*.js sweep (257 files) locally on Windows
(clang-cl build) - all pass except 7 pre-existing failures unrelated
to this change (EPERM on symlinkSync due to this environment not
running elevated/without Developer Mode, verified individually).

Fixes: #58892

`fs.readdir()`, `fs.readdirSync()`, and `fs.promises.readdir()` threw
ERR_INVALID_ARG_TYPE (or crashed the process outright in the callback
case) when called with both `{ recursive: true }` and
`{ encoding: 'buffer' }`, because the internal recursive walk used
`path.join()`, `path.relative()`, and a CommonJS-module-resolution
specific native stat binding, none of which accept Buffer arguments.
Adds `relativeToBasePath()` and `isDirectoryPath()` helpers to
`internal/fs/utils`, alongside the existing (but previously
unexported) `join()` helper, all of which handle both string and
Buffer paths. The recursive readdir implementations in `lib/fs.js`
and `lib/internal/fs/promises.js` now use these instead of calling
`path`/the module-resolution stat binding directly.
`isDirectoryPath()` falls back to the general-purpose `stat` binding
(the same one `fs.statSync()` uses) for Buffer paths, so it also
handles non-UTF8 file names correctly instead of round-tripping
through a lossy string conversion.
Fixes: nodejs#58892
Signed-off-by: Can <hello@syntaxandco.com>
@nodejs-github-botnodejs-github-bot added fs Issues and PRs related to file-system APIs and the fs module. needs-ci PRs that need a full CI run. labels Aug 2, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fsIssues and PRs related to file-system APIs and the fs module.needs-ciPRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

all versions of readdir don't work in recursive mode when used with a buffer argument

2 participants

@canblmz1@nodejs-github-bot
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' fs: fix recursive readdir with buffer encoding by canblmz1 · Pull Request #64954 · nodejs/node · GitHub
Skip to content

fs: fix recursive readdir with buffer encoding - #64954

Open
canblmz1 wants to merge 1 commit into
nodejs:mainfrom
canblmz1:fix/readdir-recursive-buffer-path
Open

fs: fix recursive readdir with buffer encoding#64954
canblmz1 wants to merge 1 commit into
nodejs:mainfrom
canblmz1:fix/readdir-recursive-buffer-path

Conversation

@canblmz1

Copy link
Copy Markdown

Description

fs.readdir(), fs.readdirSync(), and fs.promises.readdir() throw
ERR_INVALID_ARG_TYPE (or crash the process outright in the callback
case) when called with both { recursive: true } and
{ encoding: 'buffer' }. The internal recursive walk builds paths with
path.join()/path.relative(), and checks whether an entry is a
directory via a CommonJS-module-resolution-specific native stat
binding (internalModuleStat) - none of these accept Buffer
arguments, which is what entry names become once encoding: 'buffer'
is set.

constfs=require('fs');fs.readdirSync('.',{recursive: true,encoding: 'buffer'});// Uncaught TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument// must be of type string. Received an instance of Buffer

Fix

  • internal/fs/utils already had an unexported join() helper that
    correctly handles string/Buffer combinations; this exports it and
    adds two more helpers alongside it: relativeToBasePath() (avoids
    needing path.relative()'s Buffer support, since the full path is
    always built by repeatedly joining onto the base path) and
    isDirectoryPath().
  • isDirectoryPath() keeps using internalModuleStat for the string
    fast path (unchanged behavior/performance for the common case), but
    falls back to the general-purpose stat binding - the same one
    fs.statSync() uses - for Buffer paths. That binding handles
    Buffers correctly at the native layer, so this also avoids a lossy
    string round-trip for non-UTF8 file names, rather than just papering
    over the reported crash.
  • lib/fs.js (handleDirents/handleFilePaths) and
    lib/internal/fs/promises.js (readdirRecursive) both had their
    own copy of this bug and are updated to use the shared helpers.

Testing

Added test/parallel/test-fs-readdir-recursive-buffer.js, covering
readdirSync, readdirSync with withFileTypes, the readdir
callback form, and fs.promises.readdir, all with
{ recursive: true, encoding: 'buffer' }.

Ran the full test/parallel/test-fs-readdir*.js suite plus a broader
test/parallel/test-fs-*.js sweep (257 files) locally on Windows
(clang-cl build) - all pass except 7 pre-existing failures unrelated
to this change (EPERM on symlinkSync due to this environment not
running elevated/without Developer Mode, verified individually).

Fixes: #58892

`fs.readdir()`, `fs.readdirSync()`, and `fs.promises.readdir()` threw
ERR_INVALID_ARG_TYPE (or crashed the process outright in the callback
case) when called with both `{ recursive: true }` and
`{ encoding: 'buffer' }`, because the internal recursive walk used
`path.join()`, `path.relative()`, and a CommonJS-module-resolution
specific native stat binding, none of which accept Buffer arguments.
Adds `relativeToBasePath()` and `isDirectoryPath()` helpers to
`internal/fs/utils`, alongside the existing (but previously
unexported) `join()` helper, all of which handle both string and
Buffer paths. The recursive readdir implementations in `lib/fs.js`
and `lib/internal/fs/promises.js` now use these instead of calling
`path`/the module-resolution stat binding directly.
`isDirectoryPath()` falls back to the general-purpose `stat` binding
(the same one `fs.statSync()` uses) for Buffer paths, so it also
handles non-UTF8 file names correctly instead of round-tripping
through a lossy string conversion.
Fixes: nodejs#58892
Signed-off-by: Can <hello@syntaxandco.com>
@nodejs-github-botnodejs-github-bot added fs Issues and PRs related to file-system APIs and the fs module. needs-ci PRs that need a full CI run. labels Aug 2, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fsIssues and PRs related to file-system APIs and the fs module.needs-ciPRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

all versions of readdir don't work in recursive mode when used with a buffer argument

2 participants

@canblmz1@nodejs-github-bot
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fs: fix recursive readdir with buffer encoding by canblmz1 · Pull Request #64954 · nodejs/node · GitHub
Skip to content

fs: fix recursive readdir with buffer encoding - #64954

Open
canblmz1 wants to merge 1 commit into
nodejs:mainfrom
canblmz1:fix/readdir-recursive-buffer-path
Open

fs: fix recursive readdir with buffer encoding#64954
canblmz1 wants to merge 1 commit into
nodejs:mainfrom
canblmz1:fix/readdir-recursive-buffer-path

Conversation

@canblmz1

Copy link
Copy Markdown

Description

fs.readdir(), fs.readdirSync(), and fs.promises.readdir() throw
ERR_INVALID_ARG_TYPE (or crash the process outright in the callback
case) when called with both { recursive: true } and
{ encoding: 'buffer' }. The internal recursive walk builds paths with
path.join()/path.relative(), and checks whether an entry is a
directory via a CommonJS-module-resolution-specific native stat
binding (internalModuleStat) - none of these accept Buffer
arguments, which is what entry names become once encoding: 'buffer'
is set.

constfs=require('fs');fs.readdirSync('.',{recursive: true,encoding: 'buffer'});// Uncaught TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument// must be of type string. Received an instance of Buffer

Fix

  • internal/fs/utils already had an unexported join() helper that
    correctly handles string/Buffer combinations; this exports it and
    adds two more helpers alongside it: relativeToBasePath() (avoids
    needing path.relative()'s Buffer support, since the full path is
    always built by repeatedly joining onto the base path) and
    isDirectoryPath().
  • isDirectoryPath() keeps using internalModuleStat for the string
    fast path (unchanged behavior/performance for the common case), but
    falls back to the general-purpose stat binding - the same one
    fs.statSync() uses - for Buffer paths. That binding handles
    Buffers correctly at the native layer, so this also avoids a lossy
    string round-trip for non-UTF8 file names, rather than just papering
    over the reported crash.
  • lib/fs.js (handleDirents/handleFilePaths) and
    lib/internal/fs/promises.js (readdirRecursive) both had their
    own copy of this bug and are updated to use the shared helpers.

Testing

Added test/parallel/test-fs-readdir-recursive-buffer.js, covering
readdirSync, readdirSync with withFileTypes, the readdir
callback form, and fs.promises.readdir, all with
{ recursive: true, encoding: 'buffer' }.

Ran the full test/parallel/test-fs-readdir*.js suite plus a broader
test/parallel/test-fs-*.js sweep (257 files) locally on Windows
(clang-cl build) - all pass except 7 pre-existing failures unrelated
to this change (EPERM on symlinkSync due to this environment not
running elevated/without Developer Mode, verified individually).

Fixes: #58892

`fs.readdir()`, `fs.readdirSync()`, and `fs.promises.readdir()` threw
ERR_INVALID_ARG_TYPE (or crashed the process outright in the callback
case) when called with both `{ recursive: true }` and
`{ encoding: 'buffer' }`, because the internal recursive walk used
`path.join()`, `path.relative()`, and a CommonJS-module-resolution
specific native stat binding, none of which accept Buffer arguments.
Adds `relativeToBasePath()` and `isDirectoryPath()` helpers to
`internal/fs/utils`, alongside the existing (but previously
unexported) `join()` helper, all of which handle both string and
Buffer paths. The recursive readdir implementations in `lib/fs.js`
and `lib/internal/fs/promises.js` now use these instead of calling
`path`/the module-resolution stat binding directly.
`isDirectoryPath()` falls back to the general-purpose `stat` binding
(the same one `fs.statSync()` uses) for Buffer paths, so it also
handles non-UTF8 file names correctly instead of round-tripping
through a lossy string conversion.
Fixes: nodejs#58892
Signed-off-by: Can <hello@syntaxandco.com>
@nodejs-github-botnodejs-github-bot added fs Issues and PRs related to file-system APIs and the fs module. needs-ci PRs that need a full CI run. labels Aug 2, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fsIssues and PRs related to file-system APIs and the fs module.needs-ciPRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

all versions of readdir don't work in recursive mode when used with a buffer argument

2 participants

@canblmz1@nodejs-github-bot
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); fs: fix recursive readdir with buffer encoding by canblmz1 · Pull Request #64954 · nodejs/node · GitHub
Skip to content

fs: fix recursive readdir with buffer encoding - #64954

Open
canblmz1 wants to merge 1 commit into
nodejs:mainfrom
canblmz1:fix/readdir-recursive-buffer-path
Open

fs: fix recursive readdir with buffer encoding#64954
canblmz1 wants to merge 1 commit into
nodejs:mainfrom
canblmz1:fix/readdir-recursive-buffer-path

Conversation

@canblmz1

Copy link
Copy Markdown

Description

fs.readdir(), fs.readdirSync(), and fs.promises.readdir() throw
ERR_INVALID_ARG_TYPE (or crash the process outright in the callback
case) when called with both { recursive: true } and
{ encoding: 'buffer' }. The internal recursive walk builds paths with
path.join()/path.relative(), and checks whether an entry is a
directory via a CommonJS-module-resolution-specific native stat
binding (internalModuleStat) - none of these accept Buffer
arguments, which is what entry names become once encoding: 'buffer'
is set.

constfs=require('fs');fs.readdirSync('.',{recursive: true,encoding: 'buffer'});// Uncaught TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument// must be of type string. Received an instance of Buffer

Fix

  • internal/fs/utils already had an unexported join() helper that
    correctly handles string/Buffer combinations; this exports it and
    adds two more helpers alongside it: relativeToBasePath() (avoids
    needing path.relative()'s Buffer support, since the full path is
    always built by repeatedly joining onto the base path) and
    isDirectoryPath().
  • isDirectoryPath() keeps using internalModuleStat for the string
    fast path (unchanged behavior/performance for the common case), but
    falls back to the general-purpose stat binding - the same one
    fs.statSync() uses - for Buffer paths. That binding handles
    Buffers correctly at the native layer, so this also avoids a lossy
    string round-trip for non-UTF8 file names, rather than just papering
    over the reported crash.
  • lib/fs.js (handleDirents/handleFilePaths) and
    lib/internal/fs/promises.js (readdirRecursive) both had their
    own copy of this bug and are updated to use the shared helpers.

Testing

Added test/parallel/test-fs-readdir-recursive-buffer.js, covering
readdirSync, readdirSync with withFileTypes, the readdir
callback form, and fs.promises.readdir, all with
{ recursive: true, encoding: 'buffer' }.

Ran the full test/parallel/test-fs-readdir*.js suite plus a broader
test/parallel/test-fs-*.js sweep (257 files) locally on Windows
(clang-cl build) - all pass except 7 pre-existing failures unrelated
to this change (EPERM on symlinkSync due to this environment not
running elevated/without Developer Mode, verified individually).

Fixes: #58892

`fs.readdir()`, `fs.readdirSync()`, and `fs.promises.readdir()` threw
ERR_INVALID_ARG_TYPE (or crashed the process outright in the callback
case) when called with both `{ recursive: true }` and
`{ encoding: 'buffer' }`, because the internal recursive walk used
`path.join()`, `path.relative()`, and a CommonJS-module-resolution
specific native stat binding, none of which accept Buffer arguments.
Adds `relativeToBasePath()` and `isDirectoryPath()` helpers to
`internal/fs/utils`, alongside the existing (but previously
unexported) `join()` helper, all of which handle both string and
Buffer paths. The recursive readdir implementations in `lib/fs.js`
and `lib/internal/fs/promises.js` now use these instead of calling
`path`/the module-resolution stat binding directly.
`isDirectoryPath()` falls back to the general-purpose `stat` binding
(the same one `fs.statSync()` uses) for Buffer paths, so it also
handles non-UTF8 file names correctly instead of round-tripping
through a lossy string conversion.
Fixes: nodejs#58892
Signed-off-by: Can <hello@syntaxandco.com>
@nodejs-github-botnodejs-github-bot added fs Issues and PRs related to file-system APIs and the fs module. needs-ci PRs that need a full CI run. labels Aug 2, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fsIssues and PRs related to file-system APIs and the fs module.needs-ciPRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

all versions of readdir don't work in recursive mode when used with a buffer argument

2 participants

@canblmz1@nodejs-github-bot