siyuan-note/siyuan · error

path %v: value is nil

Error message

path %v: value is nil

What it means

Thrown by getJsContextValue when traversing the plugin's JS context (globalThis.siyuan.*) and the cursor along the path is a Go nil goja.Value before reaching the requested key. This indicates a parent segment in the lookup chain was never assigned (obj.Get returned nil) rather than being JS undefined/null. The %v is the path key being looked up when nil was encountered.

Source

Thrown at kernel/plugin/sandbox.go:293

}

// NewDataObject creates a new JS object with text(), json(), buffer() and arrayBuffer() methods for the given data.
func NewDataObject(p *KernelPlugin, rt *goja.Runtime, data []byte) (*goja.Object, error) {
	obj := rt.NewObject()
	if err := ObjectSetDataMethods(p, rt, obj, data); err != nil {
		return nil, err
	}
	return obj, nil
}

// getJsContextValue safely retrieves a nested value from the plugin's JS context, returning nil if any step fails.
func getJsContextValue(rt *goja.Runtime, paths []any) (value goja.Value, err error) {
	var cursor goja.Value = rt.GlobalObject()
	var path string = "globalThis"

	for _, key := range paths {
		if cursor == nil {
			err = fmt.Errorf("path %v: value is nil", key)
			return
		}

		if goja.IsUndefined(cursor) || goja.IsNull(cursor) {
			err = fmt.Errorf("path %v: value is %s", key, cursor.String())
			return
		}

		obj := cursor.ToObject(rt)
		if obj == nil {
			err = fmt.Errorf("path %v: expected object, got %T", key, cursor)
			return
		}

		switch k := key.(type) {
		case string:
			cursor = obj.Get(k)
			path = fmt.Sprintf("%s.%s", path, k)

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Ensure the plugin JS assigns globalThis.siyuan with the documented structure (event.on, server[scope][requestType].handler) before any request fires.
  2. Inspect the plugin's init/entrypoint for early throws that abort before globalThis.siyuan is assigned.
  3. Reproduce by logging rt.GlobalObject().Get("siyuan") right before the failing call to confirm the nil segment.
  4. Validate the plugin manifest/API version matches the kernel's expected global shape.

Example fix

// before (plugin entry, assignment aborted by earlier throw)
globalThis.siyuan = {}
// after
if (typeof globalThis.siyuan !== 'object' || globalThis.siyuan === null) globalThis.siyuan = {}
globalThis.siyuan.event = { on(e) { ... } }
Defensive patterns

Strategy: validation

Validate before calling

// Before invoking a kernel hook path, confirm the global exists and is object.
// (Plugin side, at registration time)
if (typeof globalThis.siyuan !== 'object' || globalThis.siyuan === null) {
  globalThis.siyuan = {}
}

Type guard

function isSiyuanGlobalObject(): boolean {
  return typeof globalThis.siyuan === 'object' && globalThis.siyuan !== null
}

Prevention

When it happens

Trigger: Calling getJsContextValue(rt, []any{"siyuan", "server", scope, requestType}) or getJsContextValue(rt, []any{"siyuan", "event"}) when a previous obj.Get returned nil — e.g. globalThis.siyuan itself is absent, or siyuan.server is nil. Happens during dispatchEvent or getRequestHandler when the plugin did not register the expected global.

Common situations: A plugin's main JS fails to set window/globalThis.siyuan = {...}; a plugin targets an older API shape; a sandboxed plugin whose top-level code threw before assignment; runtime reload after partial init.

Related errors


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