{"record":{"id":"bdb2a30406d18f91","repo":"siyuan-note/siyuan","slug":"initialize-fsnotify-watcher-w","errorCode":null,"errorMessage":"initialize fsnotify watcher: %w","messagePattern":"initialize fsnotify watcher: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/plugin/plugin.go","lineNumber":780,"sourceCode":"\t}\n}\n\n// addStorageWatch adds a path to the fsnotify watcher to watch for storage file/directory changes.\nfunc (p *KernelPlugin) addStorageWatch(path string) (err error) {\n\tif !isPluginFileWatchSupported() {\n\t\treturn errPluginFileWatchUnsupported\n\t}\n\n\tp.watcherMu.Lock()\n\tdefer p.watcherMu.Unlock()\n\n\tif contextErr := p.context.Err(); contextErr != nil {\n\t\treturn fmt.Errorf(\"plugin stopped: %w\", contextErr)\n\t}\n\tif p.watcher == nil {\n\t\tp.watcher, err = fsnotify.NewWatcher()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"initialize fsnotify watcher: %w\", err)\n\t\t}\n\t\tp.watcherDone = make(chan struct{})\n\t\tgo p.startStorageWatch(p.watcher, p.watcherDone)\n\t}\n\n\terr = p.watcher.Add(path)\n\treturn\n}\n\n// removeStorageWatch removes a path from the fsnotify watcher to stop watching for storage file/directory changes.\nfunc (p *KernelPlugin) removeStorageWatch(path string) (err error) {\n\tif !isPluginFileWatchSupported() {\n\t\treturn errPluginFileWatchUnsupported\n\t}\n\n\tp.watcherMu.Lock()\n\tdefer p.watcherMu.Unlock()\n","sourceCodeStart":762,"sourceCodeEnd":798,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/plugin/plugin.go#L762-L798","documentation":"Returned by addStorageWatch when fsnotify.NewWatcher() fails while lazily initializing the file watcher on first watch. The OS-level inotify/kqueue/ReadDirectoryChangesW error is wrapped. Once it fails p.watcher stays nil so a later Add is not attempted on a bad watcher.","triggerScenarios":"Calling siyuan.storage.watcher.add(path) for the first time on a system that is out of inotify watches/watches, has too many open file descriptors, or lacks kernel inotify support.","commonSituations":"Linux hosts with fs.inotify.max_user_watches set too low for many plugins/files, containers with a restricted procfs, or embedded/mobile builds where fsnotify is unavailable.","solutions":["On Linux raise fs.inotify.max_user_watches (sysctl) and fs.file-max, then retry the watch.","Reduce the number/depth of watched directories; watch specific files instead of trees.","Run SiYuan on a platform with fsnotify support; on mobile this path returns errPluginFileWatchUnsupported instead.","Check the kernel log for the matching ENOMEM/ENOSPC from inotify_add_watch."],"exampleFix":"# before (host default inotify limit)\n# plugin call fails with 'initialize fsnotify watcher: ...'\n\n# after (raise inotify watch limit)\nsudo sysctl fs.inotify.max_user_watches=524288\nsudo sysctl fs.inotify.max_user_instances=512","handlingStrategy":"try-catch","validationCode":"// host-level: ensure fs.inotify.max_user_watches is high enough\n// (no runtime guard from plugin JS; check platform limits before adding watches)","typeGuard":null,"tryCatchPattern":"try { await siyuan.storage.watcher.add(path); } catch (e) { if (/fsnotify|inotify/i.test(String(e))) { console.warn('watch unsupported, polling fallback', e); /* fall back to polling */ } else throw e; }","preventionTips":["Raise fs.inotify.max_user_watches on Linux hosts.","Watch specific files/subdirs, not entire trees.","Fall back to periodic polling when fsnotify add fails."],"tags":["plugin","fsnotify","watcher","linux","system-limits"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}