microsoft/typescript-go · warning
%w: watched directory removed
Error message
%w: watched directory removed
What it means
The fanotify backend detected that the root directory of a watch was itself deleted. The kernel drops the mark for a deleted root, so no further events can arrive; the backend sets a terminal error wrapping the sentinel fswatch.ErrWatchTerminated on that watch's event stream so subscribers clean up. This is an expected lifecycle signal, not a malfunction.
Source
Thrown at internal/fswatch/fanotify_linux.go:572
// later events for the (now-moved) inodes would be reported
// against stale paths. For FAN_MOVED_FROM that takes the
// inode out of our watched tree the kernel mark on the
// inode itself unfortunately leaks: fanotify has no
// path-independent way to unmark and the destination is
// outside everything we can resolve.
// Self events may not have FAN_ONDIR set (like inotify).
if isSelfMask || isDir {
b.dropSubsForPathAndDescendantsLocked(path)
} else {
b.dropSubsForPathLocked(path)
}
w.events.remove(path)
touched = true
// Root-of-watch deletion: the kernel has dropped the mark.
// Surface ErrWatchTerminated alongside the delete so callers
// know to clean up; no more events will arrive for w.
if isSelfMask && path == w.dir {
w.events.setError(fmt.Errorf("%w: watched directory removed", ErrWatchTerminated))
}
}
}
if hasCreate {
w.events.create(path)
if isDir && w.recursive {
_ = walkDir(w.physicalPath(path), true, func(p string, pIsDir bool) error {
if !pIsDir {
return nil
}
_ = b.markDir(w, w.displayPath(p), p)
return nil
})
}
touched = true
}
View on GitHub (pinned to 1bcfa18d79)
Solutions
- In the event callback, check for the error/terminated signal (errors.Is(err, fswatch.ErrWatchTerminated)) and Close the watch
- If you must keep watching, subscribe to the parent and re-WatchDirectory when the directory is recreated
- Design cleanup to remove files inside the root rather than the root itself when continuous watching is required
Example fix
// before: assumes the watch lives forever
w, _ := fswatch.Default().WatchDirectory(dir, func(ev fswatch.Event) { ... })
// after: handle terminal error, re-subscribe on recreation
w, _ := fswatch.Default().WatchDirectory(dir, func(ev fswatch.Event) {
if errors.Is(ev.Err, fswatch.ErrWatchTerminated) {
w.Close()
waitForDirToExist(dir) // e.g. via parent watch
w, _ = fswatch.Default().WatchDirectory(dir, cb) // re-subscribe
}
}) Defensive patterns
Strategy: type-guard
Validate before calling
// Optional: avoid the terminal error by watching a stable parent instead.
parent := filepath.Dir(dir)
if dir == parent { /* watching a filesystem root: no parent available */ }
w, err := fswatch.Default().WatchDirectory(parent, func(ev fswatch.Event) {
if ev.HasDelete() && ev.Path == dir { handleRootRemoved(dir) }
}) Type guard
func isWatchTerminated(err error) bool {
return err != nil && errors.Is(err, fswatch.ErrWatchTerminated)
} Try / catch
w, err := fswatch.Default().WatchDirectory(dir, cb)
if err != nil { return err }
// In the event callback / error channel:
if isWatchTerminated(ev.Err) {
w.Close() // release remaining state
go resubscribeWhenRecreated(dir, cb) // optional: re-watch on recreation
return
} Prevention
- Always handle ErrWatchTerminated in callbacks — deleted watch roots are a normal lifecycle event, not a bug
- If deletion+recreation is common (build dirs), watch the parent and re-subscribe on create
- Do not assume events keep flowing after this error; the kernel mark is gone
When it happens
Trigger: Deleting (or renaming away) the exact directory passed to WatchDirectory — the delete self-event for the watch root triggers it; also unmounting the watched directory's filesystem.
Common situations: Build systems wiping temp/build directories; users deleting a project folder while an editor watches it; `rm -rf` of a watched root during cleanup scripts; git worktree removal.
Related errors
- unable to open pipe: %w
- unable to initialize fanotify: %w
- unable to poll: %w
- fanotify_mark on '%s' failed: %w
- name_to_handle_at: %w
AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16).
Data as JSON: /api/errors/cfef0f421ae8e3dd.
Report an issue: GitHub.