Repository navigation
Description
Summary
Using a glob pattern in the ignore option causes the entire host process to crash (no catchable error) when a filesystem change event has a long relative path. On Windows this surfaces as STATUS_STACK_BUFFER_OVERRUN (exit code 3221226505 / 0xC0000409); the same root cause produces a SIGSEGV on Linux.
The crash happens inside std::regex_match (src/Glob.cc), which is run against every event's relative path. std::regex is implemented recursively (notably in MSVC's STL), so a long enough path overflows the watcher thread's stack.
This is not the compile-time invalid-regex crash fixed in #195 — the regex compiles fine; the crash is at match time, after subscribe succeeds.
Minimal reproduction
const watcher = require("@parcel/watcher");
const fs = require("fs");
const path = require("path");
const os = require("os");
(async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "pw-"));
await watcher.subscribe(root, () => {}, {
ignore: ["**/*.log"], // any glob -> compiled to std::regex in the native addon
});
console.log("subscribed");
// Create a deeply nested tree so a change event's relative path is ~300+ chars.
let cur = root;
for (let i = 0; i < 5; i++) {
cur = path.join(cur, "a".repeat(200));
fs.mkdirSync(cur);
}
console.log("created deep tree");
setTimeout(() => { console.log("survived (no crash)"); process.exit(0); }, 3000);
})();Result
subscribed
…then the process dies immediately with exit code 3221226505 (0xC0000409, STATUS_STACK_BUFFER_OVERRUN). process.on('uncaughtException') / try/catch never fire — the crash is in native code on the watcher thread, so it cannot be caught from JS.
Threshold
The crash depends on the relative path length, not depth specifically. With ignore: ["**/*.log"]:
| Relative path length | Result |
|---|---|
| ~200 chars | survives |
| ~300 chars | crash (0xC0000409) |
| ~500+ chars | crash |
Control: with ignore: [] (no globs), the same 12,000-char-deep tree is processed fine — confirming the crash is in std::regex_match, not the path handling itself.
This is an ordinary path length in real projects (e.g. deep node_modules, or git-worktree paths like …/worktree/<40-char-hash>/<branch>/…). It is much worse with non-ASCII paths: the path passed to std::regex is UTF-8, so each CJK character is 3 bytes — ~85–100 CJK chars is enough to cross the threshold.
Root cause
src/Glob.cc:
bool Glob::isIgnored(std::string relative_path) const {
return std::regex_match(relative_path, mRegex); // recursive -> stack overflow on long input
}isIgnored is called per event from Subscription::processEvent (src/windows/WindowsBackend.cc) on the watcher thread. std::regex_match recurses proportionally to input length, overflowing the thread stack.
Affected versions
- Reproduced on 2.5.1 and on latest 2.5.6 (
Glob.ccis unchanged across 2.5.1 → 2.5.6). - Verified on Windows 11 x64, Node.js v24. Given
std::regex's recursive implementation, this is expected to affect Linux/macOS as well (cf. the LinuxSIGSEGVbacktrace in Segmentation fault using wildcard in ignore option on Node.js 22.7.0 #184, which also points atstd::__detail::_Compiler/regex internals).
This appears related to #244 (the same std::regex + picomatch-generated patterns), and distinct from #184 / #194 (compile-time, fixed by #195).
Suggested fix
Replace std::regex in Glob.cc with a non-recursive matcher — e.g. RE2, or run the already-available picomatch/makeRe matching outside the native regex engine. At minimum, the match should not recurse on input length.
Environment
@parcel/watcher2.5.6 (also 2.5.1)- Windows 11 x64, Node.js v24.14.1
- backend:
windows(default on Win32)
Activity
fix(watcher): move glob ignore filtering to JS to prevent native crash
para: keep the file watcher alive on long paths
fix(ok): ok start crashes natively on non-ASCII project paths (PRD-80…
fix(ok): ok start crashes natively on non-ASCII project paths (PRD-80…
fix(ok): ok start crashes natively on non-ASCII project paths (PRD-80…
fix(ok): ok start crashes natively on non-ASCII project paths (PRD-80…
fix(ok): ok start crashes natively on non-ASCII project paths (PRD-80…
fix(ok): ok start crashes natively on non-ASCII project paths (PRD-80…
fix(ok): ok start crashes natively on non-ASCII project paths (PRD-80…
fix(ok): ok start crashes natively on non-ASCII project paths (PRD-80…
fix(ok): ok start crashes natively on non-ASCII project paths (PRD-80…
fix(ok): ok start crashes natively on non-ASCII project paths (PRD-80…
fix(ok): ok start crashes natively on non-ASCII project paths (PRD-80…