{"record":{"id":"9b732cdbbdfd09c6","repo":"siyuan-note/siyuan","slug":"fsnotify-watcher-not-initialized","errorCode":null,"errorMessage":"fsnotify watcher not initialized","messagePattern":"fsnotify watcher not initialized","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"kernel/plugin/plugin.go","lineNumber":800,"sourceCode":"\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\n\tif p.watcher == nil {\n\t\terr = fmt.Errorf(\"fsnotify watcher not initialized\")\n\t\treturn\n\t}\n\n\terr = p.watcher.Remove(path)\n\treturn\n}\n\nfunc (p *KernelPlugin) closeStorageWatcher() {\n\tp.watcherMu.Lock()\n\twatcher := p.watcher\n\tdone := p.watcherDone\n\tp.watcher = nil\n\tp.watcherDone = nil\n\tp.watcherMu.Unlock()\n\n\tif watcher != nil {\n\t\twatcher.Close()\n\t}","sourceCodeStart":782,"sourceCodeEnd":818,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/plugin/plugin.go#L782-L818","documentation":"Returned by removeStorageWatch when a plugin tries to stop watching a path but no fsnotify watcher exists (p.watcher == nil). The watcher is created lazily on the first addStorageWatch, so removing before ever adding, or after the watcher was closed, hits this guard.","triggerScenarios":"Calling siyuan.storage.watcher.remove(path) before siyuan.storage.watcher.add(path), or after closeStorageWatcher has torn the watcher down during plugin unload.","commonSituations":"Plugin lifecycle bug: cleanup runs before setup, or onunload/duplicate-remove fires after the plugin context was cancelled and the watcher closed.","solutions":["Track watch state in the plugin and only call remove for paths you successfully added.","Ignore this specific error during teardown (it is benign if the watcher is already gone).","Ensure add() resolved before registering the corresponding remove() in onunload."],"exampleFix":"// before\nonunload(() => { siyuan.storage.watcher.remove(path); });\n\n// after\nlet added = false;\nawait siyuan.storage.watcher.add(path).then(() => { added = true; });\nonunload(() => {\n  if (!added) return;\n  siyuan.storage.watcher.remove(path).catch(() => {});\n});","handlingStrategy":"try-catch","validationCode":"// track whether add() succeeded so remove() is only called when relevant\nconst watched = new Set<string>();\nawait siyuan.storage.watcher.add(path).then(() => watched.add(path));","typeGuard":null,"tryCatchPattern":"try { await siyuan.storage.watcher.remove(path); } catch (e) { if (/not initialized/.test(String(e))) return; throw e; }","preventionTips":["Only remove paths you successfully added.","Swallow 'not initialized' during teardown; it is benign.","Order onunload cleanup after add() resolves."],"tags":["plugin","fsnotify","watcher","lifecycle"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}