siyuan-note/siyuan · warning
fsnotify watcher not initialized
Error message
fsnotify watcher not initialized
What it means
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.
Source
Thrown at kernel/plugin/plugin.go:800
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 251596fc0d)
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.
Example fix
// before
onunload(() => { siyuan.storage.watcher.remove(path); });
// after
let added = false;
await siyuan.storage.watcher.add(path).then(() => { added = true; });
onunload(() => {
if (!added) return;
siyuan.storage.watcher.remove(path).catch(() => {});
}); Defensive patterns
Strategy: try-catch
Validate before calling
// track whether add() succeeded so remove() is only called when relevant const watched = new Set<string>(); await siyuan.storage.watcher.add(path).then(() => watched.add(path));
Try / catch
try { await siyuan.storage.watcher.remove(path); } catch (e) { if (/not initialized/.test(String(e))) return; throw e; } Prevention
- Only remove paths you successfully added.
- Swallow 'not initialized' during teardown; it is benign.
- Order onunload cleanup after add() resolves.
When it happens
Trigger: Calling siyuan.storage.watcher.remove(path) before siyuan.storage.watcher.add(path), or after closeStorageWatcher has torn the watcher down during plugin unload.
Common situations: Plugin lifecycle bug: cleanup runs before setup, or onunload/duplicate-remove fires after the plugin context was cancelled and the watcher closed.
Related errors
- initialize fsnotify watcher: %w
- failed to add storage path to watcher: %v
- failed to remove storage path from watcher: %v
- WebSocket is not open (state: %d)
- Recorder has been disposed
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/9b732cdbbdfd09c6.
Report an issue: GitHub.