{"record":{"id":"fe028ca7ddec800b","repo":"microsoft/typescript-go","slug":"fanotify-mark-on-s-failed-w","errorCode":null,"errorMessage":"fanotify_mark on '%s' failed: %w","messagePattern":"fanotify_mark on '(.+?)' failed: %w","errorType":"exception","errorClass":"dirWatchError","httpStatus":null,"severity":"error","filePath":"internal/fswatch/fanotify_linux.go","lineNumber":326,"sourceCode":"\t\t\t\t// attached for the life of the process, but since\n\t\t\t\t// markDir below will Add the real mask with the same\n\t\t\t\t// flags the kernel just merges them. The probe is the\n\t\t\t\t// only failure path we explicitly retry.\n\t\t\t\tfor {\n\t\t\t\t\trmErr := unix.FanotifyMark(b.fanotifyFD, unix.FAN_MARK_REMOVE|unix.FAN_MARK_ONLYDIR, fanotifyMarkMaskRename, unix.AT_FDCWD, w.physicalDir)\n\t\t\t\t\tif rmErr == nil || !errors.Is(rmErr, unix.EINTR) {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase errors.Is(err, unix.EINVAL), errors.Is(err, unix.EOPNOTSUPP):\n\t\t\t\tb.markMask = fanotifyMarkMaskMovedFromTo\n\t\t\t}\n\t\t}\n\t}\n\tif !w.recursive {\n\t\tif err := b.markDir(w, w.dir, w.physicalDir); err != nil {\n\t\t\treturn &dirWatchError{\n\t\t\t\terr:      fmt.Errorf(\"fanotify_mark on '%s' failed: %w\", w.dir, err),\n\t\t\t\tdirWatch: w,\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif err := walkDir(w.physicalDir, true, func(watchPath string, isDir bool) error {\n\t\tif !isDir {\n\t\t\treturn nil\n\t\t}\n\t\tpath := w.displayPath(watchPath)\n\t\tif err := b.markDir(w, path, watchPath); err != nil {\n\t\t\treturn &dirWatchError{\n\t\t\t\terr:      fmt.Errorf(\"fanotify_mark on '%s' failed: %w\", path, err),\n\t\t\t\tdirWatch: w,\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}); err != nil {","sourceCodeStart":308,"sourceCodeEnd":344,"githubUrl":"https://github.com/microsoft/typescript-go/blob/1bcfa18d79a3be41772223d5c05dfe4480e614ff/internal/fswatch/fanotify_linux.go#L308-L344","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Raise fs.fanotify.max_user_marks (sysctl) if errno is ENOSPC, or watch fewer directories (narrow includes, ignore node_modules)","Check errors.Is(err, fswatch.ErrFilesystemUnsupported) and switch that path to inotify or polling","Grant CAP_SYS_ADMIN / run privileged if the errno is EPERM on your kernel","Watch the closest existing ancestor and re-subscribe when the target directory is created instead of racing deletions"],"exampleFix":"# before: ENOSPC after ~8k marks (default max_user_marks=8192)\nsudo sysctl -w fs.fanotify.max_user_marks=65536\n# and/or prune the tree in Go:\nw, err := fswatch.Default().WatchDirectory(dir, cb, fswatch.WithRecursive(), fswatch.WithIgnore(func(p string) bool { return strings.Contains(p, \"node_modules\") }))","handlingStrategy":"fallback","validationCode":"// Pre-flight the sysctl budget for mark-heavy watches.\nfunc markBudget() (cur int64, err error) {\n    b, err := os.ReadFile(\"/proc/sys/fs/fanotify/max_user_marks\")\n    if err != nil { return 0, err }\n    return strconv.ParseInt(strings.TrimSpace(string(b)), 10, 64)\n}","typeGuard":"func isMarkFailure(err error) bool {\n    var dw *fswatch.DirWatchError // if exposed; otherwise match on message\n    return err != nil && strings.Contains(err.Error(), \"fanotify_mark\")\n}\n\nfunc isUnsupportedFS(err error) bool {\n    return err != nil && errors.Is(err, fswatch.ErrFilesystemUnsupported)\n}","tryCatchPattern":"w, err := fswatch.Default().WatchDirectory(dir, cb, opts...)\nif err != nil {\n    switch {\n    case isUnsupportedFS(err):\n        w, err = fswatch.Inotify().WatchDirectory(dir, cb, opts...) // different fs access path\n    case isMarkFailure(err):\n        sysctlSet(\"fs.fanotify.max_user_marks\", \"65536\") // or prompt the user to\n        w, err = fswatch.Default().WatchDirectory(dir, cb, opts...)\n    }\n}\nif err != nil { return err }","preventionTips":["Set fs.fanotify.max_user_marks generously on dev machines/CI hosts that watch big trees","Prefer one recursive watch with ignore filters over many single-directory watches","Remember marks persist per uid across processes: unrelated fanotify users consume the same quota"],"tags":["linux","fanotify","sysctl","fswatch","kernel-quota"],"backgroundTag":null,"analyzedSha":"1bcfa18d79a3be41772223d5c05dfe4480e614ff","analyzedAt":"2026-08-16T02:12:00.115Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}