siyuan-note/siyuan · warning

failed to remove storage path from watcher: %v

Error message

failed to remove storage path from watcher: %v

What it means

Thrown when p.removeStorageWatch(abs) returns an error after path resolution. The kernel tried to stop watching the given absolute path but the underlying watcher reported failure — most often because the path was never actually being watched, or the watcher had already been closed. The wrapped underlying error replaces %v.

Source

Thrown at kernel/plugin/api_storage.go:120

		var path string
		if len(call.Arguments) >= 1 && goja.IsString(call.Argument(0)) {
			path = call.Argument(0).String()
		} else {
			argErr = fmt.Errorf("path required")
		}

		runErr := p.worker.Run(func(rt *goja.Runtime) (result any, err error) {
			if argErr != nil {
				err = argErr
				return
			}
			abs, resolveErr := resolvePath(path)
			if resolveErr != nil {
				err = resolveErr
				return
			}
			if removeErr := p.removeStorageWatch(abs); removeErr != nil {
				err = fmt.Errorf("failed to remove storage path from watcher: %v", removeErr)
			}
			return
		}, func(rt *goja.Runtime, result any, err error) {
			if lo.IsNil(err) {
				if resolveErr := resolve(result); resolveErr != nil {
					logging.LogErrorf("[plugin:%s] siyuan.storage.watcher.remove resolve: %v", p.Name, resolveErr)
				}
			} else {
				if rejectErr := reject(rt.NewGoError(err)); rejectErr != nil {
					logging.LogErrorf("[plugin:%s] siyuan.storage.watcher.remove reject: %v", p.Name, rejectErr)
				}
			}
		})
		if runErr != nil {
			logging.LogErrorf("[plugin:%s] siyuan.storage.watcher.remove worker run: %v", p.Name, runErr)
			if rejectErr := reject(rt.NewGoError(runErr)); rejectErr != nil {
				logging.LogErrorf("[plugin:%s] siyuan.storage.watcher.remove reject: %v", p.Name, rejectErr)
			}

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Maintain a Set of currently-watched relative paths and only remove entries present in it.
  2. Make remove idempotent: catch the rejection and ignore 'not watched' style errors.
  3. Remove each path exactly once during shutdown.

Example fix

// before
await siyuan.storage.watcher.remove('cache/state.json'); // may reject
// after
try {
  await siyuan.storage.watcher.remove('cache/state.json');
} catch (e) { /* already removed or never added */ }
watchedPaths.delete('cache/state.json');
Defensive patterns

Strategy: fallback

Validate before calling

const watched = new Set();
// only remove paths we know we added:
if (!watched.has(path)) return;

Try / catch

try {
  await siyuan.storage.watcher.remove(path);
} catch (e) {
  // already removed or never added: safe to ignore
}
watched.delete(path);

Prevention

When it happens

Trigger: Calling watcher.remove on a path that was never added via watcher.add; calling remove twice for the same path; removing a path after the watcher was torn down during plugin unload.

Common situations: Plugin loses track of which paths it is watching and removes a stale entry; duplicate shutdown handlers fire remove more than once; the plugin reloaded and the in-memory watch set was reset but the JS side still thinks it is watching.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/0e7fc25c197f7b70. Report an issue: GitHub.