siyuan-note/siyuan · error

The current kernel is in read-only mode, storage.remove is…

Error message

The current kernel is in read-only mode, storage.remove is not allowed

What it means

siyuan.storage.remove is a mutating plugin-storage API. When the SiYuan kernel runs in read-only mode (util.ReadOnly is true, e.g. while a sync/data operation holds exclusive access or the workspace is opened read-only), the API refuses to delete files and rejects the returned Promise with this message. This guards plugin data from being deleted while the kernel cannot safely persist changes.

Solutions

  1. Check whether the kernel is in read-only mode before calling siyuan.storage.remove (kernel.plugin supports a read-only query; or catch the rejection and defer the removal)
  2. Wrap remove calls in try/catch and queue deletions for when read-only mode ends
  3. Avoid destructive storage cleanup on plugin startup; only remove entries the user explicitly deletes
  4. If read-only mode is unexpected, restart the kernel/workspace with write access enabled

Example fix

// before
await siyuan.storage.remove("/tmp/cache.json");

// after
try {
  await siyuan.storage.remove("/tmp/cache.json");
} catch (e) {
  if (String(e).includes("read-only mode")) {
    console.warn("deferred removal: kernel is read-only");
  } else {
    throw e;
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// guard: skip removal while kernel is read-only
if (typeof siyuan?.kernel?.isReadOnly === "function" && siyuan.kernel.isReadOnly()) {
  return; // defer removal
}

Try / catch

try {
  await siyuan.storage.remove(path);
} catch (e) {
  if (String(e).includes("read-only mode")) {
    // queue for later
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: A plugin calls siyuan.storage.remove(path) while the kernel is in read-only mode. In the Go kernel this check runs before path resolution, so even a valid path is rejected. Typically hit when SiYuan is running with the read-only flag or during states where writes are disabled.

Common situations: Workspace opened in read-only mode; kernel started with a read-only configuration; plugin code that assumes storage is always writable and calls remove unconditionally (e.g. cleanup logic on plugin load).

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/7b4b032c92d84052. Report an issue: GitHub.

Appendix: source

Thrown at kernel/plugin/api_storage.go:324

	// siyuan.storage.remove(path) -> Promise<void>
	lo.Must0(storage.Set("remove", rt.ToValue(func(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
		promise, resolve, reject := rt.NewPromise()

		var argErr error
		var path string
		if len(call.Arguments) < 1 {
			argErr = fmt.Errorf("path required")
		} else {
			path = call.Argument(0).String()
		}

		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)
					}

View on GitHub (pinned to 9f775e8a12)