siyuan-note/siyuan · warning
fsnotify watcher not initialized
Error message
fsnotify watcher not initialized
What it means
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.
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
Example fix
// before
plugin.removeStorageWatch(path);
// after
if (watched.has(path)) {
watched.delete(path);
plugin.removeStorageWatch(path);
} Defensive patterns
Strategy: try-catch
Validate before calling
const watched = new Set();
function safeUnwatch(p) {
if (!watched.has(p)) return; // never watched; skip
watched.delete(p);
plugin.removeStorageWatch(p);
} Try / catch
try {
plugin.removeStorageWatch(path);
} catch (e) {
if (String(e).includes("not initialized")) {
return; // nothing to remove; treat as no-op
}
throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- plugin stopped
- globalThis.siyuan.plugin.lifecycle is not an object
- globalThis.siyuan.plugin.lifecycle not found
- globalThis.siyuan.plugin.lifecycle.
- initialize fsnotify watcher
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/9b732cdbbdfd09c6.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/plugin/plugin.go:801
p.watcherDone = make(chan struct{})
go p.startStorageWatch(p.watcher, p.watcherDone)
}
err = p.watcher.Add(path)
return
}
// removeStorageWatch removes a path from the fsnotify watcher to stop watching for storage file/directory changes.
func (p *KernelPlugin) removeStorageWatch(path string) (err error) {
if !isPluginFileWatchSupported() {
return errPluginFileWatchUnsupported
}
p.watcherMu.Lock()
defer p.watcherMu.Unlock()
if p.watcher == nil {
err = fmt.Errorf("fsnotify watcher not initialized")
return
}
err = p.watcher.Remove(path)
return
}
func (p *KernelPlugin) closeStorageWatcher() {
p.watcherMu.Lock()
watcher := p.watcher
done := p.watcherDone
p.watcher = nil
p.watcherDone = nil
p.watcherMu.Unlock()
if watcher != nil {
watcher.Close()
}View on GitHub (pinned to 9f775e8a12)