From f95b2fc5cc530a1b6d0b4f9a4e8ccbf5cd831212 Mon Sep 17 00:00:00 2001 From: Diarica Date: Sun, 12 Jul 2026 22:12:52 +0800 Subject: [PATCH] fix(Windows): reject absolute paths from fs.watch recursive to prevent RangeError MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Windows, fs.watch with {recursive: true} can deliver file-change events for paths OUTSIDE the watched directory via ReadDirectoryChangesW. A common trigger is system services writing to %ProgramData% (e.g., Autodesk CER), which produces filenames like \ProgramData\Autodesk\CER. After normalizePath converts backslashes, these arrive as "/ProgramData/Autodesk/CER" — an absolute-looking path that the ignore package rejects with "path should be a `path.relative()`d string". Add a path.isAbsolute() guard in handleChange() (right after the existing empty/./.. filter) so that any absolute path from the recursive watcher is silently dropped before it reaches the ignore matcher. Legitimate project-relative changes are never absolute, so this is always safe. Fixes the MCP server crash for any Windows user with Autodesk software installed (or any other system service that writes to %ProgramData%). --- src/sync/watcher.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/sync/watcher.ts b/src/sync/watcher.ts index 059f54334..ba41e1578 100644 --- a/src/sync/watcher.ts +++ b/src/sync/watcher.ts @@ -549,6 +549,14 @@ export class FileWatcher { */ private handleChange(rel: string): void { if (!rel || rel === '.' || rel.startsWith('..')) return; + // Windows fs.watch recursive can deliver events from outside the project + // root via ReadDirectoryChangesW (e.g., %ProgramData% paths from system + // services). After normalizePath these arrive as absolute-looking paths + // like "/ProgramData/Autodesk/CER", which would crash the ignore matcher + // with "path should be a `path.relative()`d string" RangeError. + // Reject any absolute path here — a legitimate project-relative change + // is never absolute. + if (path.isAbsolute(rel)) return; if (this.isAlwaysIgnored(rel)) return; if (this.ignoreMatcher && this.ignoreMatcher.ignores(rel)) return; if (!isSourceFile(rel, loadExtensionOverrides(this.projectRoot))) return;