siyuan-note/siyuan · error

path %v: value is %s

Error message

path %v: value is %s

What it means

Thrown by getJsContextValue when a path segment resolves to a JS undefined or null value (goja.IsUndefined || goja.IsNull). Unlike the nil case, the value exists in goja terms but is the JS absence value. The %s is cursor.String() ("undefined" or "null").

Source

Thrown at kernel/plugin/sandbox.go:298

	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)
		case int:
			cursor = obj.Get(strconv.Itoa(k))
			path = fmt.Sprintf("%s[%d]", path, k)
		default:
			err = fmt.Errorf("unsupported path type: %T", key)

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Confirm every scope/requestType the kernel will query is populated in globalThis.siyuan.server[scope][requestType] as a non-null object.
  2. Guard the plugin with typeof checks before assignment so partial registration cannot leave undefined holes.
  3. Match the plugin's registration to the kernel's AccessScope/RequestType string constants exactly (case-sensitive).

Example fix

// before
globalThis.siyuan.server = { network: { /* missing other scope */ } }
// after
const server = globalThis.siyuan.server || (globalThis.siyuan.server = {})
server.network = server.network || {}
server.network.beforeRequest = { handler: (req) => { ... } }
Defensive patterns

Strategy: validation

Validate before calling

// Plugin side: ensure scope/requestType objects exist before assigning handler.
const server = (globalThis.siyuan.server ??= {})
const scope = (server['network'] ??= {})
if (scope['beforeRequest'] == null) scope['beforeRequest'] = {}

Type guard

function scopeEntryExists(scope: string, rt: string): boolean {
  const n = globalThis.siyuan?.server?.[scope]?.[rt]
  return n !== undefined && n !== null
}

Prevention

When it happens

Trigger: Traversing into globalThis.siyuan.server[scope][requestType] where an intermediate object exists but the requested property is undefined/null — e.g. siyuan.server is defined but the scope key was never populated, or event handler object is null.

Common situations: Plugin registers siyuan.server but forgets a specific AccessScope (e.g. registers "network" but kernel requests "filebox"); plugin sets a hook to null intentionally but kernel tries to read a sub-key; API contract mismatch between kernel and plugin.

Related errors


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