Version
main
Platform
Subsystem
vfs
What steps will reproduce the bug?
import vfs from 'node:vfs';
const fs = vfs.create({ emitExperimentalWarning: false });
fs.mkdirSync('/a/b', { recursive: true });
fs.writeFileSync('/a/file.txt', 'data');
try {
fs.renameSync('/a', '/a/b/c');
console.log('rename succeeded');
} catch (err) {
console.log('rename threw:', err.code);
}
console.log('/ entries:', fs.readdirSync('/'));
console.log('/a exists:', fs.existsSync('/a'));
console.log('/a/b/c exists:', fs.existsSync('/a/b/c'));
How often does it reproduce? Is there a required condition?
Always
What is the expected behavior? Why is that the expected behavior?
The rename should throw, typically EINVAL, and the tree should remain unchanged. A directory cannot be moved into its own descendant because that would create a cycle or orphan the directory hierarchy.
What do you see instead?
rename succeeded
/ entries: []
/a exists: false
/a/b/c exists: false
The implementation moves /a into one of its own children, then removes the original root entry, detaching the whole subtree.
Additional information
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'native-rename-'));
try {
fs.mkdirSync(path.join(dir, 'a/b'), { recursive: true });
fs.writeFileSync(path.join(dir, 'a/file.txt'), 'data');
try {
fs.renameSync(path.join(dir, 'a'), path.join(dir, 'a/b/c'));
console.log('rename succeeded');
} catch (err) {
console.log('rename threw:', err.code);
}
console.log('root entries:', fs.readdirSync(dir));
console.log('a exists:', fs.existsSync(path.join(dir, 'a')));
console.log('a/b/c exists:', fs.existsSync(path.join(dir, 'a/b/c')));
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
Output
rename threw: EINVAL
root entries: [ 'a' ]
a exists: true
a/b/c exists: false
Version
main
Platform
Subsystem
vfs
What steps will reproduce the bug?
How often does it reproduce? Is there a required condition?
Always
What is the expected behavior? Why is that the expected behavior?
The rename should throw, typically
EINVAL, and the tree should remain unchanged. A directory cannot be moved into its own descendant because that would create a cycle or orphan the directory hierarchy.What do you see instead?
The implementation moves
/ainto one of its own children, then removes the original root entry, detaching the whole subtree.Additional information
Output