siyuan-note/siyuan · error
cannot remove storage root
Error message
cannot remove storage root
What it means
Thrown by siyuan.storage.remove() when the resolved absolute path equals the plugin storage root directory itself (abs == p.storageDir). The kernel forbids deleting the storage sandbox root because doing so would break subsequent storage operations and the watcher bookkeeping. The guard fires after read-only and path-resolution checks but before the delete goroutine.
Source
Thrown at kernel/plugin/api_storage.go:334
}
runErr := p.worker.Run(func(rt *goja.Runtime) (result any, err error) {
if argErr != nil {
err = argErr
return
}
if util.ReadOnly {
err = fmt.Errorf("The current kernel is in read-only mode, storage.remove is not allowed")
return
}
abs, resolveErr := resolvePath(path)
if resolveErr != nil {
err = resolveErr
return
}
if abs == p.storageDir {
err = fmt.Errorf("cannot remove storage root")
return
}
go func() (result any, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic during siyuan.storage.remove: %v", r)
}
p.worker.Run(func(rt *goja.Runtime) (_ any, _ error) {
if lo.IsNil(err) {
if resolveErr := resolve(result); resolveErr != nil {
logging.LogErrorf("[plugin:%s] siyuan.storage.remove resolve: %v", p.Name, resolveErr)
}
} else {
if rejectErr := reject(rt.NewGoError(err)); rejectErr != nil {
logging.LogErrorf("[plugin:%s] siyuan.storage.remove reject: %v", p.Name, rejectErr)
}View on GitHub (pinned to 251596fc0d)
Solutions
- List the children (e.g. via your own index) and remove them one by one instead of the root.
- Reject empty or root-equivalent paths in your plugin code before calling remove.
- Document that the storage root is reserved and cannot be deleted.
Example fix
// before
await siyuan.storage.remove(''); // intends to clear all
// after
for (const name of Object.keys(await listStorageEntries())) {
await siyuan.storage.remove(name);
} Defensive patterns
Strategy: validation
Validate before calling
function assertRemovable(rel) {
if (typeof rel !== 'string' || rel.length === 0 || rel === '.' || rel === './' || rel === '.') {
throw new Error('refusing to remove storage root');
}
} Type guard
const isNonRootPath = (p) => typeof p === 'string' && p.length > 0 && p !== '.' && p !== './' && p !== '/' && p !== '\\';
Try / catch
try { await siyuan.storage.remove(path); }
catch (e) { if (/cannot remove storage root/.test(String(e))) { /* clear children instead */ } else throw e; } Prevention
- Never call remove('') or remove('.') to mean 'clear all'.
- Enumerate and delete children individually for a clear-all feature.
When it happens
Trigger: Calling remove('') or remove('.') or remove('./') — any path that filepath.Clean collapses to the storage root. Also remove with an empty string passed intentionally to mean 'everything'.
Common situations: Plugin implements a 'clear all' feature by calling remove('') or remove('.') instead of enumerating children; a path-building bug yields an empty relative path that resolves to the root.
Related errors
- failed to add storage path to watcher: %v
- failed to make directory: %w
- failed to write file: %w
- save OAuth client registration: %w
- save OAuth credentials: %w
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/39bcc3722f398739.
Report an issue: GitHub.