{"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":801,"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":783,"sourceCodeEnd":819,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/plugin/plugin.go#L783-L819","documentation":"removeStorageWatch removes a path from the plugin's fsnotify watcher, but only if the watcher was previously created by addStorageWatch. If p.watcher is nil, no watcher exists, and the call fails with 'fsnotify watcher not initialized' instead of silently no-oping.","triggerScenarios":"Calling removeStorageWatch (unwatch) for a path without ever having called addStorageWatch in the plugin's lifetime, or after the watcher was closed/teared down on plugin stop.","commonSituations":"Cleanup handlers that unwatch paths never watched; double-unwatch; unwatch called during teardown after the watcher was already closed; tests exercising unwatch-before-watch ordering.","solutions":["Only call removeStorageWatch for paths previously registered with addStorageWatch","Track watched paths in a Set and skip unwatch if absent","Treat this error as a no-op in cleanup code (catch and ignore)","Re-add the watch before removing if the watcher was recreated"],"exampleFix":"// before\nplugin.removeStorageWatch(path);\n\n// after\nif (watched.has(path)) {\n  watched.delete(path);\n  plugin.removeStorageWatch(path);\n}","handlingStrategy":"try-catch","validationCode":"const watched = new Set();\nfunction safeUnwatch(p) {\n  if (!watched.has(p)) return; // never watched; skip\n  watched.delete(p);\n  plugin.removeStorageWatch(p);\n}","typeGuard":null,"tryCatchPattern":"try {\n  plugin.removeStorageWatch(path);\n} catch (e) {\n  if (String(e).includes(\"not initialized\")) {\n    return; // nothing to remove; treat as no-op\n  }\n  throw e;\n}","preventionTips":["Track watched paths in a Set; only unwatch registered paths","Skip unwatch during teardown if the watcher was already closed","Make unwatch calls idempotent in cleanup handlers"],"tags":["plugin","fsnotify","watcher","lifecycle"],"backgroundTag":"invalid-state-transition","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}