microsoft/typescript-go · error

fswatch: failed to watch directory %q: %w

Error message

fswatch: failed to watch directory %q: %w

What it means

Error from the fallback watcher's per-request re-subscribe path. When the primary (fanotify) batch fails with ErrFilesystemUnsupported, each request is retried individually; this error means that for request.Dir both the fanotify primary and the inotify secondary failed. The message names the directory and wraps the underlying error, and every watch already created for earlier requests in the batch is rolled back before returning.

Source

Thrown at internal/fswatch/watcher.go:284

	watches, err := w.primary.WatchDirectories(requests)
	if err == nil || !errors.Is(err, ErrFilesystemUnsupported) {
		return watches, err
	}

	watches = make([]Watch, 0, len(requests))
	rollback := func() {
		for i := len(watches) - 1; i >= 0; i-- {
			_ = watches[i].Close()
		}
	}
	for _, request := range requests {
		watch, err := w.primary.WatchDirectory(request.Dir, request.Callback, request.Options...)
		if errors.Is(err, ErrFilesystemUnsupported) {
			watch, err = w.secondary.WatchDirectory(request.Dir, request.Callback, request.Options...)
		}
		if err != nil {
			rollback()
			return nil, fmt.Errorf("fswatch: failed to watch directory %q: %w", request.Dir, err)
		}
		watches = append(watches, watch)
	}
	return watches, nil
}

func (w *fallbackWatcher) WatchFile(path string, fn WatchCallback) (Watch, error) {
	watch, err := w.primary.WatchFile(path, fn)
	if errors.Is(err, ErrFilesystemUnsupported) {
		return w.secondary.WatchFile(path, fn)
	}
	return watch, err
}

func (w *fallbackWatcher) unexported() {}

// watcher is the concrete implementation of [Watcher]. Each platform
// watcher is a package-level *watcher whose factory is set by the

View on GitHub (pinned to 1bcfa18d79)

Solutions

  1. Unwrap the error to identify the cause: ENOSPC means raise fs.inotify.max_user_watches and fs.inotify.max_user_instances via sysctl
  2. Verify the directory exists at subscribe time; re-check and retry if it is created asynchronously
  3. Trim recursive watch scope to reduce kernel watch consumption
  4. Move the tree off the unsupported filesystem, or run outside the container

Example fix

// before
watches, err := fswatch.Fanotify().WatchDirectories(reqs)
if err != nil { return err }

// after
watches, err := fswatch.Fanotify().WatchDirectories(reqs)
if err != nil {
    var errno syscall.Errno
    if errors.As(err, &errno) && errno == unix.ENOSPC {
        // raise fs.inotify.max_user_watches, then retry
    }
    return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

if _, err := os.Stat(dir); err != nil {
    // create or wait for the directory before batch-subscribing
}

Try / catch

watches, err := fswatch.Fanotify().WatchDirectories(reqs)
if err != nil {
    var errno syscall.Errno
    if errors.As(err, &errno) {
        switch errno {
        case unix.ENOSPC:
            // raise fs.inotify.max_user_watches / max_user_instances, retry
        case unix.EACCES:
            // fix permissions on dir
        case unix.ENOENT:
            // recreate dir, retry
        }
    }
}

Prevention

When it happens

Trigger: Fanotify() watching a filesystem it cannot handle (overlayfs, virtiofs, FUSE) where inotify then also fails: the directory disappeared, inotify instance or watch limits were hit (ENOSPC from fs.inotify.max_user_watches), or permissions deny the watch.

Common situations: Deep trees inside Docker containers where fanotify is unsupported and the inotify watch limit is also exceeded. Directories removed between the primary batch attempt and the secondary retry.

Related errors


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