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

  1. List the children (e.g. via your own index) and remove them one by one instead of the root.
  2. Reject empty or root-equivalent paths in your plugin code before calling remove.
  3. 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

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


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/39bcc3722f398739. Report an issue: GitHub.