{"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/9f775e8a12daef8255556097396f9b2739078892/kernel/plugin/api_storage.go#L316-L352","documentation":"siyuan.storage.remove resolves the given path inside the plugin's storage directory and refuses to delete the storage root itself (abs == p.storageDir). Deleting the root would wipe all plugin storage, so the API rejects with 'cannot remove storage root'.","triggerScenarios":"Calling siyuan.storage.remove(\"\") or siyuan.storage.remove(\"/\") or any path that resolves exactly to the plugin storage directory root. Path resolution normalizes the input, so any spelling that lands on the root triggers this.","commonSituations":"Plugin cleanup code that computes a path from an empty variable or default; calling remove with no meaningful segment; a loop that deletes all entries and also targets the root.","solutions":["Pass a path that points to a specific file or subdirectory, not the root","Guard the call: skip removal when the path is empty or resolves to the root","To clear all data, iterate siyuan.storage.list('/') and remove each entry individually instead of removing the root"],"exampleFix":"// before\nawait siyuan.storage.remove(path || \"/\");\n\n// after\nif (path && path !== \"/\") {\n  await siyuan.storage.remove(path);\n}","handlingStrategy":"validation","validationCode":"function canRemove(p) {\n  const norm = \"/\" + String(p ?? \"\").replace(/^\\/+/, \"\");\n  return norm !== \"/\" && norm.length > 1;\n}\nif (!canRemove(path)) throw new Error(\"refusing to remove storage root\");","typeGuard":null,"tryCatchPattern":"try {\n  await siyuan.storage.remove(path);\n} catch (e) {\n  if (String(e).includes(\"storage root\")) {\n    console.warn(\"path resolved to storage root; skipped\");\n  } else throw e;\n}","preventionTips":["Never pass empty strings or '/' to storage.remove","Guard default parameter values that may be empty","To wipe data, enumerate entries with list and delete them individually"],"tags":["plugin","storage","path","validation"],"backgroundTag":"invalid-argument-value","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}