microsoft/typescript-go · error · dirWatchError

fanotify_mark on '%s' failed: %w

Error message

fanotify_mark on '%s' failed: %w

What it means

Adding a non-recursive watch failed at its single fanotify_mark(FAN_MARK_ADD) call on the watch directory; the raw errno is wrapped and the failure is reported as a dirWatchError naming the offending directory. Marks consume kernel quota and require supported filesystems, so the errno usually points at quota, permissions, or filesystem support.

Source

Thrown at internal/fswatch/fanotify_linux.go:326

				// attached for the life of the process, but since
				// markDir below will Add the real mask with the same
				// flags the kernel just merges them. The probe is the
				// only failure path we explicitly retry.
				for {
					rmErr := unix.FanotifyMark(b.fanotifyFD, unix.FAN_MARK_REMOVE|unix.FAN_MARK_ONLYDIR, fanotifyMarkMaskRename, unix.AT_FDCWD, w.physicalDir)
					if rmErr == nil || !errors.Is(rmErr, unix.EINTR) {
						break
					}
				}
			case errors.Is(err, unix.EINVAL), errors.Is(err, unix.EOPNOTSUPP):
				b.markMask = fanotifyMarkMaskMovedFromTo
			}
		}
	}
	if !w.recursive {
		if err := b.markDir(w, w.dir, w.physicalDir); err != nil {
			return &dirWatchError{
				err:      fmt.Errorf("fanotify_mark on '%s' failed: %w", w.dir, err),
				dirWatch: w,
			}
		}
		return nil
	}
	if err := walkDir(w.physicalDir, true, func(watchPath string, isDir bool) error {
		if !isDir {
			return nil
		}
		path := w.displayPath(watchPath)
		if err := b.markDir(w, path, watchPath); err != nil {
			return &dirWatchError{
				err:      fmt.Errorf("fanotify_mark on '%s' failed: %w", path, err),
				dirWatch: w,
			}
		}
		return nil
	}); err != nil {

View on GitHub (pinned to 1bcfa18d79)

Solutions

  1. Raise fs.fanotify.max_user_marks (sysctl) if errno is ENOSPC, or watch fewer directories (narrow includes, ignore node_modules)
  2. Check errors.Is(err, fswatch.ErrFilesystemUnsupported) and switch that path to inotify or polling
  3. Grant CAP_SYS_ADMIN / run privileged if the errno is EPERM on your kernel
  4. Watch the closest existing ancestor and re-subscribe when the target directory is created instead of racing deletions

Example fix

# before: ENOSPC after ~8k marks (default max_user_marks=8192)
sudo sysctl -w fs.fanotify.max_user_marks=65536
# and/or prune the tree in Go:
w, err := fswatch.Default().WatchDirectory(dir, cb, fswatch.WithRecursive(), fswatch.WithIgnore(func(p string) bool { return strings.Contains(p, "node_modules") }))
Defensive patterns

Strategy: fallback

Validate before calling

// Pre-flight the sysctl budget for mark-heavy watches.
func markBudget() (cur int64, err error) {
    b, err := os.ReadFile("/proc/sys/fs/fanotify/max_user_marks")
    if err != nil { return 0, err }
    return strconv.ParseInt(strings.TrimSpace(string(b)), 10, 64)
}

Type guard

func isMarkFailure(err error) bool {
    var dw *fswatch.DirWatchError // if exposed; otherwise match on message
    return err != nil && strings.Contains(err.Error(), "fanotify_mark")
}

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 {
    switch {
    case isUnsupportedFS(err):
        w, err = fswatch.Inotify().WatchDirectory(dir, cb, opts...) // different fs access path
    case isMarkFailure(err):
        sysctlSet("fs.fanotify.max_user_marks", "65536") // or prompt the user to
        w, err = fswatch.Default().WatchDirectory(dir, cb, opts...)
    }
}
if err != nil { return err }

Prevention

When it happens

Trigger: Exceeding /proc/sys/fs/fanotify/max_user_marks (ENOSPC) when watching many directories; EPERM where the mark flags need privilege; EINVAL/EOPNOTSUPP/ENODEV on filesystems fanotify FID mode cannot handle (some FUSE, overlayfs, fuseblk NTFS — the latter wrapped as ErrFilesystemUnsupported); target deleted between stat and mark.

Common situations: Recursive-in-userspace watches over huge monorepos exhausting mark quota; dev containers on overlayfs/virtiofs bind mounts; network filesystems; missing CAP_SYS_ADMIN on older kernels.

Related errors


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