siyuan-note/siyuan · error
path %v: expected object, got %T
Error message
path %v: expected object, got %T
What it means
Thrown by getJsContextValue when cursor.ToObject(rt) returns nil — the cursor value is neither nil nor goja-undefined/null but cannot be converted to a goja.Object (e.g. a primitive string/number/boolean). The %T reports the underlying Go type. Means code tried to index into a non-object value.
Source
Thrown at kernel/plugin/sandbox.go:304
// 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)
return
}
}
value = cursor
return
}View on GitHub (pinned to 251596fc0d)
Solutions
- Audit globalThis.siyuan assignments in the plugin: every node the kernel traverses (siyuan, siyuan.server, scope, requestType) must be a plain object.
- Add a typeof === 'object' && !Array.isArray guard in plugin code before assigning each level.
- If the error names a primitive type (string/int/bool), grep the plugin source for direct reassignment of that path.
Example fix
// before
globalThis.siyuan = JSON.stringify({server:{}}) // oops: string
// after
globalThis.siyuan = { server: {} } Defensive patterns
Strategy: type-guard
Validate before calling
// Reject non-object intermediate before the kernel traverses it.
function assertObjectAt(path: string[]): void {
let cur: any = globalThis
for (const k of path) {
cur = cur[k]
if (typeof cur !== 'object' || cur === null || Array.isArray(cur)) {
throw new Error(`path ${path.join('.')}: expected object at ${k}`)
}
}
} Type guard
function isPlainObject(v: unknown): v is Record<string, unknown> {
return typeof v === 'object' && v !== null && !Array.isArray(v)
} Prevention
- Never assign a primitive where the kernel expects an object (siyuan, siyuan.server, scope, requestType).
- Add a typeof === 'object' check in plugin code before each assignment.
- Avoid JSON.stringify of config objects into the path the kernel traverses.
When it happens
Trigger: globalThis.siyuan was set to a string/number instead of an object; siyuan.server was set to a function or primitive; an intermediate path was reassigned from object to scalar by buggy plugin code.
Common situations: Plugin author writes globalThis.siyuan = "..."; a hook overwrote an object path with a scalar return value; serialization round-trip flattened an object to a string.
Related errors
- path %v: value is nil
- path %v: value is %s
- expected promise object, got %T
- globalThis.siyuan.server[%s][%s] is not an object
- siyuan.server[%s][%s].handler is not a function
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/bb733b463bc94fd5.
Report an issue: GitHub.