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
- Maintain a Set of currently-watched relative paths and only remove entries present in it.
- Make remove idempotent: catch the rejection and ignore 'not watched' style errors.
- 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
- Make remove idempotent by catching the rejection.
- Remove each path exactly once during shutdown.
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
- failed to add storage path to watcher: %v
- path required
- siyuan.storage: path traversal not allowed
- panic during siyuan.storage.get: %v
- path and content required
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/0e7fc25c197f7b70.
Report an issue: GitHub.