microsoft/typescript-go · error

%w: %w

Error message

%w: %w

What it means

maybeWrapUnsupportedFilesystem upgrades fanotify syscall errors whose errno is EOPNOTSUPP, ENOTSUP, or ENODEV into a dual-wrapped error that also carries the sentinel fswatch.ErrFilesystemUnsupported ('fswatch: watcher backend unsupported on this filesystem'). This is the library's contract for 'this backend cannot watch THIS filesystem', letting callers branch on errors.Is and pick a different backend while the original errno stays inspectable.

Source

Thrown at internal/fswatch/fanotify_linux.go:374

	if err != nil {
		// Unmark since we can't track this directory without a handle.
		_ = unix.FanotifyMark(b.fanotifyFD, unix.FAN_MARK_REMOVE|unix.FAN_MARK_ONLYDIR, b.markMask, unix.AT_FDCWD, markPath)
		return maybeWrapUnsupportedFilesystem(fmt.Errorf("name_to_handle_at: %w", err))
	}
	var st unix.Statfs_t
	if err := unix.Statfs(markPath, &st); err != nil {
		_ = unix.FanotifyMark(b.fanotifyFD, unix.FAN_MARK_REMOVE|unix.FAN_MARK_ONLYDIR, b.markMask, unix.AT_FDCWD, markPath)
		return fmt.Errorf("statfs: %w", err)
	}
	key := makeFanotifyHandleKey(st.Fsid.Val, handle.Type(), handle.Bytes())
	sub := &fanotifySubscription{path: path, watchPath: markPath, dirWatch: w, key: key}
	b.subscriptions[key] = append(b.subscriptions[key], sub)
	return nil
}

func maybeWrapUnsupportedFilesystem(err error) error {
	if errors.Is(err, unix.EOPNOTSUPP) || errors.Is(err, unix.ENOTSUP) || errors.Is(err, unix.ENODEV) {
		return fmt.Errorf("%w: %w", err, ErrFilesystemUnsupported)
	}
	return err
}

// handleEvents reads and dispatches fanotify events from the fd.
func (b *fanotifyBackend) handleEvents() error {
	buf := b.readBuf
	watchersTouched := b.watchersTouched

	for {
		n, err := unix.Read(b.fanotifyFD, buf)
		if err != nil {
			if errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EWOULDBLOCK) {
				break
			}
			return fmt.Errorf("Error reading from fanotify: %w", err)
		}
		if n == 0 {

View on GitHub (pinned to 1bcfa18d79)

Solutions

  1. Branch on errors.Is(err, fswatch.ErrFilesystemUnsupported) and retry the subscription with fswatch.Inotify() or a polling watcher
  2. Move the working tree to a native Linux filesystem when inotify fanout cost matters
  3. Keep the raw errno (errors.Is/errno extraction) for diagnostics when reporting upstream

Example fix

// before: treat any error as fatal
w, err := fswatch.Default().WatchDirectory(dir, cb)
if err != nil { log.Fatal(err) }

// after
w, err := fswatch.Default().WatchDirectory(dir, cb)
if err != nil {
    if errors.Is(err, fswatch.ErrFilesystemUnsupported) {
        w, err = fswatch.Inotify().WatchDirectory(dir, cb)
    }
    if err != nil { log.Fatal(err) }
}
Defensive patterns

Strategy: fallback

Type guard

func isUnsupportedFS(err error) bool {
    return err != nil && errors.Is(err, fswatch.ErrFilesystemUnsupported)
}

Try / catch

w, err := fswatch.Default().WatchDirectory(dir, cb, opts...)
if err != nil {
    if errors.Is(err, fswatch.ErrFilesystemUnsupported) {
        // Errno stays wrapped for diagnostics; switch backend for this path.
        log.Printf("fanotify unsupported on %s (%v); using inotify", dir, err)
        w, err = fswatch.Inotify().WatchDirectory(dir, cb, opts...)
    }
    if err != nil { return err }
}

Prevention

When it happens

Trigger: fanotify_mark or name_to_handle_at returning EOPNOTSUPP/ENOTSUP (FUSE/virtiofs/gRPC-FUSE mounts without file-handle support) or ENODEV (e.g. NTFS via fuseblk); any WatchDirectory on the fanotify backend against such a mount surfaces this wrapper.

Common situations: macOS Docker Desktop / Podman sharing code into Linux VMs; WSL2 /drv mounts; CI on FUSE-backed checkouts.

Related errors


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