Skip to content

Commit 4d7996f

Browse files
authored
fs: ignore deleted dirs in recursive watch scan
A recursively watched directory can be removed after a parent watcher observes it but before the non-native recursive watcher scans it. Ignore ENOENT from the directory scan so this deletion race does not emit an unhandled watcher error. Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com> Assisted-by: openai:gpt-5.5 PR-URL: #63686 Refs: https://github.com/nodejs/reliability/blob/main/reports/2026-06-01.md#jstest-failure Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
1 parent 7b08df7 commit 4d7996f

2 files changed

Lines changed: 47 additions & 1 deletion

File tree

lib/internal/fs/recursive_watch.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,9 @@ class FSWatcher extends EventEmitter {
157157
}
158158
}
159159
} catch (error) {
160-
this.emit('error', error);
160+
if (error.code !== 'ENOENT') {
161+
this.emit('error', error);
162+
}
161163
}
162164
}
163165

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Flags: --expose-internals
2+
'use strict';
3+
4+
const common = require('../common');
5+
const tmpdir = require('../common/tmpdir');
6+
const assert = require('assert');
7+
const fs = require('fs');
8+
const path = require('path');
9+
const { kFSWatchStart } = require('internal/fs/watchers');
10+
const { FSWatcher } = require('internal/fs/recursive_watch');
11+
12+
if (common.isIBMi)
13+
common.skip('IBMi does not support `fs.watch()`');
14+
15+
tmpdir.refresh();
16+
17+
const parent = tmpdir.resolve('parent');
18+
const child = path.join(parent, 'child');
19+
fs.mkdirSync(child, { recursive: true });
20+
fs.writeFileSync(path.join(child, 'test.tmp'), 'test');
21+
22+
const watch = fs.watch;
23+
let deletedChild = false;
24+
fs.watch = function(filename, ...args) {
25+
const watcher = Reflect.apply(watch, this, [filename, ...args]);
26+
27+
if (filename === child) {
28+
fs.rmSync(child, { recursive: true });
29+
deletedChild = true;
30+
}
31+
32+
return watcher;
33+
};
34+
35+
const watcher = new FSWatcher({ recursive: true });
36+
watcher.on('error', common.mustNotCall());
37+
38+
try {
39+
watcher[kFSWatchStart](parent);
40+
assert.strictEqual(deletedChild, true);
41+
} finally {
42+
watcher.close();
43+
fs.watch = watch;
44+
}

0 commit comments

Comments
 (0)