You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
constwatcher=require("@parcel/watcher");constfs=require("fs");constpath=require("path");constos=require("os");(async()=>{constroot=fs.mkdtempSync(path.join(os.tmpdir(),"pw-"));awaitwatcher.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.letcur=root;for(leti=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:
boolGlob::isIgnored(std::string relative_path) const {
returnstd::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.cc is 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 Linux SIGSEGV backtrace in Segmentation fault using wildcard in ignore option on Node.js 22.7.0 #184, which also points at std::__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.
Summary
Using a glob pattern in the
ignoreoption causes the entire host process to crash (no catchable error) when a filesystem change event has a long relative path. On Windows this surfaces asSTATUS_STACK_BUFFER_OVERRUN(exit code3221226505/0xC0000409); the same root cause produces aSIGSEGVon Linux.The crash happens inside
std::regex_match(src/Glob.cc), which is run against every event's relative path.std::regexis 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
subscribesucceeds.Minimal reproduction
Result
…then the process dies immediately with exit code
3221226505(0xC0000409, STATUS_STACK_BUFFER_OVERRUN).process.on('uncaughtException')/try/catchnever 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"]:Control: with
ignore: [](no globs), the same 12,000-char-deep tree is processed fine — confirming the crash is instd::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 tostd::regexis UTF-8, so each CJK character is 3 bytes — ~85–100 CJK chars is enough to cross the threshold.Root cause
src/Glob.cc:isIgnoredis called per event fromSubscription::processEvent(src/windows/WindowsBackend.cc) on the watcher thread.std::regex_matchrecurses proportionally to input length, overflowing the thread stack.Affected versions
Glob.ccis unchanged across 2.5.1 → 2.5.6).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::regexinGlob.ccwith a non-recursive matcher — e.g. RE2, or run the already-available picomatch/makeRematching 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(default on Win32)