{"record":{"id":"39bcc3722f398739","repo":"siyuan-note/siyuan","slug":"cannot-remove-storage-root","errorCode":null,"errorMessage":"cannot remove storage root","messagePattern":"cannot remove storage root","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/plugin/api_storage.go","lineNumber":334,"sourceCode":"\t\t}\n\n\t\trunErr := p.worker.Run(func(rt *goja.Runtime) (result any, err error) {\n\t\t\tif argErr != nil {\n\t\t\t\terr = argErr\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif util.ReadOnly {\n\t\t\t\terr = fmt.Errorf(\"The current kernel is in read-only mode, storage.remove is not allowed\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tabs, resolveErr := resolvePath(path)\n\t\t\tif resolveErr != nil {\n\t\t\t\terr = resolveErr\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif abs == p.storageDir {\n\t\t\t\terr = fmt.Errorf(\"cannot remove storage root\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tgo func() (result any, err error) {\n\t\t\t\tdefer func() {\n\t\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\t\terr = fmt.Errorf(\"panic during siyuan.storage.remove: %v\", r)\n\t\t\t\t\t}\n\n\t\t\t\t\tp.worker.Run(func(rt *goja.Runtime) (_ any, _ error) {\n\t\t\t\t\t\tif lo.IsNil(err) {\n\t\t\t\t\t\t\tif resolveErr := resolve(result); resolveErr != nil {\n\t\t\t\t\t\t\t\tlogging.LogErrorf(\"[plugin:%s] siyuan.storage.remove resolve: %v\", p.Name, resolveErr)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif rejectErr := reject(rt.NewGoError(err)); rejectErr != nil {\n\t\t\t\t\t\t\t\tlogging.LogErrorf(\"[plugin:%s] siyuan.storage.remove reject: %v\", p.Name, rejectErr)\n\t\t\t\t\t\t\t}","sourceCodeStart":316,"sourceCodeEnd":352,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/plugin/api_storage.go#L316-L352","documentation":"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.","triggerScenarios":"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'.","commonSituations":"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.","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."],"exampleFix":"// before\nawait siyuan.storage.remove(''); // intends to clear all\n// after\nfor (const name of Object.keys(await listStorageEntries())) {\n  await siyuan.storage.remove(name);\n}","handlingStrategy":"validation","validationCode":"function assertRemovable(rel) {\n  if (typeof rel !== 'string' || rel.length === 0 || rel === '.' || rel === './' || rel === '.') {\n    throw new Error('refusing to remove storage root');\n  }\n}","typeGuard":"const isNonRootPath = (p) => typeof p === 'string' && p.length > 0 && p !== '.' && p !== './' && p !== '/' && p !== '\\\\';","tryCatchPattern":"try { await siyuan.storage.remove(path); }\ncatch (e) { if (/cannot remove storage root/.test(String(e))) { /* clear children instead */ } else throw e; }","preventionTips":["Never call remove('') or remove('.') to mean 'clear all'.","Enumerate and delete children individually for a clear-all feature."],"tags":["plugin-api","storage","safety-guard","filesystem"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}