{"record":{"id":"6094092c990316aa","repo":"microsoft/typescript-go","slug":"w-watched-directory-removed-609409","errorCode":null,"errorMessage":"%w: watched directory removed","messagePattern":"%w: watched directory removed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/fswatch/kqueue.go","lineNumber":331,"sourceCode":"\t\t\t\t// kernels (OpenBSD in particular) deliver only the\n\t\t\t\t// parent's NOTE_DELETE/NOTE_RENAME and never fire\n\t\t\t\t// NOTE_DELETE on the children; without this cleanup,\n\t\t\t\t// modifying a file inside the moved tree later\n\t\t\t\t// surfaces an event against the descendant's stale\n\t\t\t\t// (pre-rename) path. We also emit a delete for each\n\t\t\t\t// descendant we close, so callers don't miss those\n\t\t\t\t// removals if the kernel didn't fire per-child events.\n\t\t\t\t// (When the kernel does fire them, our follow-up\n\t\t\t\t// handleFileEvent finds the fd already gone and is a\n\t\t\t\t// no-op, so events.create's coalescing handles dups.)\n\t\t\t\tif entry.isDir {\n\t\t\t\t\tb.closeDescendantFDsLocked(sub.dirWatch, sub.entries, sub.path)\n\t\t\t\t}\n\t\t\t\tremoveEntryAndDescendants(sub.entries, sub.path)\n\t\t\t\t// Root-of-watch deletion: no more events can fire\n\t\t\t\t// for this dirWatch. Tell the caller.\n\t\t\t\tif sub.path == sub.dirWatch.dir {\n\t\t\t\t\tsub.dirWatch.events.setError(fmt.Errorf(\"%w: watched directory removed\", ErrWatchTerminated))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif !recreated {\n\t\t\tdelete(b.subsByPath, entry.path)\n\t\t}\n\t\treturn\n\t}\n\n\tfor _, sub := range subs {\n\t\ttouched[sub.dirWatch] = struct{}{}\n\t\tif fflags&(unix.NOTE_WRITE|unix.NOTE_ATTRIB|unix.NOTE_EXTEND) != 0 {\n\t\t\tsub.dirWatch.events.update(sub.path)\n\t\t}\n\t}\n}\n\n// closeDescendantFDsLocked closes every fd attached to an entry whose","sourceCodeStart":313,"sourceCodeEnd":349,"githubUrl":"https://github.com/microsoft/typescript-go/blob/1bcfa18d79a3be41772223d5c05dfe4480e614ff/internal/fswatch/kqueue.go#L313-L349","documentation":"Terminal error delivered on the WatchCallback when the root directory of a kqueue watch (macOS/BSD) is deleted or renamed away. handleFileEvent fires this after it closes the root fd and every descendant fd, because no further events can ever fire for that dirWatch. The message wraps ErrWatchTerminated, so errors.Is(err, fswatch.ErrWatchTerminated) matches.","triggerScenarios":"Call WatchDirectory on the Kqueue() watcher, then delete (rm -rf) or rename (mv) the watched directory itself. Also delivered for WatchFile when the parent directory of the file is removed. Deleting only a child of the watch root does not fire it; the watch survives.","commonSituations":"Build scripts that run 'rm -rf out && mkdir out'. Test harnesses removing temp directories. Git branch switches or git clean replacing directories. Watching directories that a package manager recreates during install.","solutions":["Handle ErrWatchTerminated in the callback and call Close() on the Watch to release remaining state","Re-subscribe with WatchDirectory once the directory has been recreated; poll for its existence first","If the directory is deleted and recreated routinely, watch its stable parent instead and filter events for the child path","Layer a polling fallback for paths whose delete/recreate churn makes re-watching unreliable"],"exampleFix":"// before\nw.WatchDirectory(dir, func(events []fswatch.Event, err error) {\n    if err != nil {\n        log.Fatal(err) // crashes when the watch root is deleted\n    }\n    apply(events)\n})\n\n// after\nwatch, err := w.WatchDirectory(dir, func(events []fswatch.Event, err error) {\n    if err != nil {\n        if errors.Is(err, fswatch.ErrWatchTerminated) {\n            watch.Close()\n            go resubscribeWhenDirExists(dir) // poll, then WatchDirectory again\n        }\n        return\n    }\n    apply(events)\n})","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"func isWatchTerminated(err error) bool {\n    return errors.Is(err, fswatch.ErrWatchTerminated)\n}","tryCatchPattern":"watch, _ := w.WatchDirectory(dir, func(events []fswatch.Event, err error) {\n    if errors.Is(err, fswatch.ErrWatchTerminated) {\n        watch.Close()\n        resubscribeWhenDirExists(dir)\n        return\n    }\n    // normal processing\n})","preventionTips":["Never delete or rename a watch root during its lifetime; replace contents instead","Watch a stable parent directory when the target has a delete/recreate lifecycle","Code the ErrWatchTerminated branch in every callback; it is a documented outcome, not an anomaly","Call Close on terminated watches so per-directory state is released"],"tags":["kqueue","macos","bsd","terminal","watch-lifecycle","filesystem"],"backgroundTag":null,"analyzedSha":"1bcfa18d79a3be41772223d5c05dfe4480e614ff","analyzedAt":"2026-08-16T02:12:00.115Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}