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
- Ensure the plugin JS assigns globalThis.siyuan with the documented structure (event.on, server[scope][requestType].handler) before any request fires.
- Inspect the plugin's init/entrypoint for early throws that abort before globalThis.siyuan is assigned.
- Reproduce by logging rt.GlobalObject().Get("siyuan") right before the failing call to confirm the nil segment.
- 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
- Always initialize globalThis.siyuan = {} at the very top of plugin entry, before any code that can throw.
- Treat every kernel-traversed path segment as required: siyuan, siyuan.server, siyuan.event.
- Log a warning if a required segment is missing at load time rather than letting the kernel fail later.
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
- path %v: value is %s
- path %v: expected object, got %T
- synchronous function returned a Promise
- expected promise object, got %T
- 'promise.then property is not a function
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/db4f2b27df69a683.
Report an issue: GitHub.