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
- 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)
- Wrap remove calls in try/catch and queue deletions for when read-only mode ends
- Avoid destructive storage cleanup on plugin startup; only remove entries the user explicitly deletes
- 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
- Never perform destructive storage cleanup unconditionally at plugin startup
- Check kernel read-only state before mutating storage APIs
- Catch and classify rejections from storage.remove
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
- failed to read directory
- failed to remove
- attribute view definition is not a regular file
- cannot remove storage root
- failed to add storage path to watcher
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)