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

  1. Only call removeStorageWatch for paths previously registered with addStorageWatch
  2. Track watched paths in a Set and skip unwatch if absent
  3. Treat this error as a no-op in cleanup code (catch and ignore)
  4. 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

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


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)