Version
v24.13.1 (also reproduced with v22 via npx node@22)
Platform
Microsoft Windows NT 10.0.26200.0 x64 (Windows 11 Pro)
Subsystem
fs
What steps will reproduce the bug?
Watch a directory with fs.watch() (non-recursive), then delete that directory. The watcher starts emitting rename events with the watched directory's own path as fast as the event loop can run (~190,000 events per second), and never stops — not even after the directory is re-created with the same name.
// repro.mjs
import { watch, mkdirSync, rmSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
const dir = join(tmpdir(), `fswatch-repro-${process.pid}`);
mkdirSync(dir, { recursive: true });
writeFileSync(join(dir, 'a.txt'), 'x');
let count = 0;
const kinds = new Map();
const w = watch(dir, (event, file) => {
count += 1;
const k = `${event} ${file}`;
kinds.set(k, (kinds.get(k) ?? 0) + 1);
});
w.on('error', (e) => console.log('error event:', e.code));
w.on('close', () => console.log('close event'));
const tick = (label) => console.log(label, count, [...kinds.entries()].slice(0, 3));
await new Promise((r) => setTimeout(r, 1000));
tick('before delete');
rmSync(dir, { recursive: true, force: true });
for (let i = 1; i <= 3; i += 1) { await new Promise((r) => setTimeout(r, 1000)); tick(`${i}s after delete`); }
mkdirSync(dir, { recursive: true });
for (let i = 1; i <= 3; i += 1) { await new Promise((r) => setTimeout(r, 1000)); tick(`${i}s after re-create`); }
w.close();
Run: node repro.mjs
How often does it reproduce? Is there a required condition?
Every time, on two machines' worth of runs (Node 24.13.1 and Node 22). The directory must be the watched directory itself (deleting a child file/directory behaves normally). NTFS local drive (C: and E:).
What is the expected behavior? Why is that the expected behavior?
Per the fs.watch caveats in the docs: "On Windows, no events will be emitted if the watched directory is moved or renamed. An EPERM error is reported when the watched directory is deleted." So either an error event (EPERM) and the watcher closing, or at most a single rename event for the directory — not an unbounded stream.
What do you see instead?
before delete 0 []
1s after delete 191393 [ [ 'rename a.txt', 1 ], [ 'rename \\\\?\\C:\\Users\\...\\fswatch-repro-21780', 191392 ] ]
2s after delete 379277 [ ... 379276 ]
3s after delete 574586 [ ... 574585 ]
1s after re-create 764369 [ ... 764368 ]
2s after re-create 952505 [ ... 952504 ]
3s after re-create 1138742 [ ... 1138741 ]
No error event, no close event — only rename events carrying the watched directory's own (\\?\-prefixed) path, ~190k/s, forever. One Node thread sits at 100% CPU.
Additional information
How we hit it: a Vite dev server (chokidar on top of fs.watch) was watching apps/*/coverage. Vitest's coverage run deletes and re-creates that directory. After that, the dev server used ~1.3 CPU cores continuously with nobody touching the app (measured with the inspector: 64% of samples in FSWatcher._handle.onchange → chokidar's handler; one FSWatcher handle emitted 306,559 rename events in 5 seconds). Two dev servers had accumulated 22 h and 9 h of CPU time before we noticed. Workaround on our side: exclude those directories from the watcher (server.watch.ignored).
process.versions.uv = 1.51.0. Related but different: #31702 (no event on move/delete), #25301 (Linux path), chokidar#1463 (watch stops after deletion — the opposite symptom).
Version
v24.13.1 (also reproduced with v22 via
npx node@22)Platform
Subsystem
fs
What steps will reproduce the bug?
Watch a directory with
fs.watch()(non-recursive), then delete that directory. The watcher starts emittingrenameevents with the watched directory's own path as fast as the event loop can run (~190,000 events per second), and never stops — not even after the directory is re-created with the same name.Run:
node repro.mjsHow often does it reproduce? Is there a required condition?
Every time, on two machines' worth of runs (Node 24.13.1 and Node 22). The directory must be the watched directory itself (deleting a child file/directory behaves normally). NTFS local drive (
C:andE:).What is the expected behavior? Why is that the expected behavior?
Per the
fs.watchcaveats in the docs: "On Windows, no events will be emitted if the watched directory is moved or renamed. AnEPERMerror is reported when the watched directory is deleted." So either anerrorevent (EPERM) and the watcher closing, or at most a singlerenameevent for the directory — not an unbounded stream.What do you see instead?
No
errorevent, nocloseevent — onlyrenameevents carrying the watched directory's own (\\?\-prefixed) path, ~190k/s, forever. One Node thread sits at 100% CPU.Additional information
How we hit it: a Vite dev server (chokidar on top of
fs.watch) was watchingapps/*/coverage. Vitest's coverage run deletes and re-creates that directory. After that, the dev server used ~1.3 CPU cores continuously with nobody touching the app (measured with the inspector: 64% of samples inFSWatcher._handle.onchange→ chokidar's handler; oneFSWatcherhandle emitted 306,559renameevents in 5 seconds). Two dev servers had accumulated 22 h and 9 h of CPU time before we noticed. Workaround on our side: exclude those directories from the watcher (server.watch.ignored).process.versions.uv= 1.51.0. Related but different: #31702 (no event on move/delete), #25301 (Linux path), chokidar#1463 (watch stops after deletion — the opposite symptom).