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

  1. In the event callback, check for the error/terminated signal (errors.Is(err, fswatch.ErrWatchTerminated)) and Close the watch
  2. If you must keep watching, subscribe to the parent and re-WatchDirectory when the directory is recreated
  3. 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

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


AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16). Data as JSON: /api/errors/cfef0f421ae8e3dd. Report an issue: GitHub.