siyuan-note/siyuan · error
failed to add storage path to watcher
Error message
failed to add storage path to watcher: %v
What it means
siyuan.storage.watcher.add registers a path with the kernel's filesystem watcher via p.addStorageWatch. When the underlying watcher refuses the path (already watched, does not exist, OS limit reached), the promise rejects with 'failed to add storage path to watcher: %v' wrapping the cause.
Solutions
- Read the wrapped cause to distinguish not-exists vs already-watched vs limit-exceeded
- Ensure the path exists inside plugin storage before calling watcher.add
- Deduplicate: track which paths are already watched and skip re-adding
- Raise the OS watch limit (e.g. fs.inotify.max_user_watches on Linux) if exhausted
- Create the target directory first (storage.set a placeholder or mkdir) then add the watch
Example fix
// before
await siyuan.storage.watcher.add('logs/') // dir may not exist
// after
await siyuan.storage.set('logs/.keep', '')
if (!watched.has('logs/')) {
watched.add('logs/')
await siyuan.storage.watcher.add('logs/')
} Defensive patterns
Strategy: try-catch
Validate before calling
if (typeof path !== 'string' || !path) throw new TypeError('watcher path required') Try / catch
try { await siyuan.storage.watcher.add(rel) } catch (e) { if (String(e.message).includes('failed to add storage path to watcher')) { /* ensure path exists / skip duplicates / raise OS watch limit */ } else throw e } Prevention
- Ensure the target path exists before watching
- Track already-watched paths to avoid duplicate registration
- Monitor OS watch limits on long-running instances
- Re-register watches after plugin reload from a persisted list with deduplication
When it happens
Trigger: Calling storage.watcher.add('relpath') where the resolved absolute path cannot be added: directory missing, path already being watched, inotify/FSEvents watch limit exhausted, or permission problems.
Common situations: Watching a file that was deleted or not yet created; adding the same path twice across plugin reloads; long-running kernels hitting the OS inotify watch limit; restricted filesystems that disallow watches.
Related errors
- attribute view definition is not a regular file
- failed to read directory
- failed to remove storage path from watcher
- failed to remove
- The current kernel is in read-only mode, storage.remove is…
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/73e1ae11e367436c.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/plugin/api_storage.go:73
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 addErr := p.addStorageWatch(abs); addErr != nil {
err = fmt.Errorf("failed to add storage path to watcher: %v", addErr)
}
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.add resolve: %v", p.Name, resolveErr)
}
} else {
if rejectErr := reject(rt.NewGoError(err)); rejectErr != nil {
logging.LogErrorf("[plugin:%s] siyuan.storage.watcher.add reject: %v", p.Name, rejectErr)
}
}
})
if runErr != nil {
logging.LogErrorf("[plugin:%s] siyuan.storage.watcher.add worker run: %v", p.Name, runErr)
if rejectErr := reject(rt.NewGoError(runErr)); rejectErr != nil {
logging.LogErrorf("[plugin:%s] siyuan.storage.watcher.add reject: %v", p.Name, rejectErr)
}View on GitHub (pinned to 9f775e8a12)