siyuan-note/siyuan · error

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

Error message

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

What it means

Thrown by siyuan.storage.put() when util.ReadOnly is true. The kernel can run in read-only mode (e.g. a published or preview instance where writes are forbidden); in that mode all storage mutations are rejected before any filesystem access. The check runs after argument validation but before path resolution and writing.

Source

Thrown at kernel/plugin/api_storage.go:247

	lo.Must0(storage.Set("put", rt.ToValue(func(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
		promise, resolve, reject := rt.NewPromise()

		var argErr error
		var path, content string
		if len(call.Arguments) < 2 {
			argErr = fmt.Errorf("path and content required")
		} else {
			path = call.Argument(0).String()
			content = call.Argument(1).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.put is not allowed")
				return
			}

			abs, resolveErr := resolvePath(path)
			if resolveErr != nil {
				err = resolveErr
				return
			}

			go func() (result any, err error) {
				defer func() {
					if r := recover(); r != nil {
						err = fmt.Errorf("panic during siyuan.storage.put: %v", r)
					}

					p.worker.Run(func(rt *goja.Runtime) (_ any, _ error) {
						if lo.IsNil(err) {
							if resolveErr := resolve(result); resolveErr != nil {

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Detect read-only mode at plugin startup and disable write features or switch to in-memory state.
  2. Wrap put in try/catch and degrade gracefully when writes are rejected.
  3. Surface a user-facing notice that the feature requires a writable kernel.

Example fix

// before
await siyuan.storage.put('state.json', body);
// after
try {
  await siyuan.storage.put('state.json', body);
} catch (e) {
  if (String(e).includes('read-only mode')) {
    notifyUser('This plugin needs a writable kernel.');
  } else {
    throw e;
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Detect read-only mode once at startup by probing a write:
let readOnly = false;
try { await siyuan.storage.put('__probe', '1'); await siyuan.storage.remove('__probe'); }
catch (e) { readOnly = /read-only mode/.test(String(e)); }

Try / catch

try {
  await siyuan.storage.put(path, body);
} catch (e) {
  if (/read-only mode/.test(String(e))) {
    notifyUser('Write features require a writable kernel.');
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling storage.put on a kernel started with the read-only flag, or on a hosted/published SiYuan instance that enforces util.ReadOnly. Any put call will reject regardless of path validity.

Common situations: Plugin developed against a normal desktop kernel is later loaded on a read-only publishing server; CI/demo environments booted read-only to prevent mutation; the user launched the kernel with a read-only workspace flag.

Related errors


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