{"record":{"id":"bb733b463bc94fd5","repo":"siyuan-note/siyuan","slug":"path-v-expected-object-got-t","errorCode":null,"errorMessage":"path %v: expected object, got %T","messagePattern":"path (.+?): expected object, got %T","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/plugin/sandbox.go","lineNumber":304,"sourceCode":"// getJsContextValue safely retrieves a nested value from the plugin's JS context, returning nil if any step fails.\nfunc getJsContextValue(rt *goja.Runtime, paths []any) (value goja.Value, err error) {\n\tvar cursor goja.Value = rt.GlobalObject()\n\tvar path string = \"globalThis\"\n\n\tfor _, key := range paths {\n\t\tif cursor == nil {\n\t\t\terr = fmt.Errorf(\"path %v: value is nil\", key)\n\t\t\treturn\n\t\t}\n\n\t\tif goja.IsUndefined(cursor) || goja.IsNull(cursor) {\n\t\t\terr = fmt.Errorf(\"path %v: value is %s\", key, cursor.String())\n\t\t\treturn\n\t\t}\n\n\t\tobj := cursor.ToObject(rt)\n\t\tif obj == nil {\n\t\t\terr = fmt.Errorf(\"path %v: expected object, got %T\", key, cursor)\n\t\t\treturn\n\t\t}\n\n\t\tswitch k := key.(type) {\n\t\tcase string:\n\t\t\tcursor = obj.Get(k)\n\t\t\tpath = fmt.Sprintf(\"%s.%s\", path, k)\n\t\tcase int:\n\t\t\tcursor = obj.Get(strconv.Itoa(k))\n\t\t\tpath = fmt.Sprintf(\"%s[%d]\", path, k)\n\t\tdefault:\n\t\t\terr = fmt.Errorf(\"unsupported path type: %T\", key)\n\t\t\treturn\n\t\t}\n\t}\n\tvalue = cursor\n\treturn\n}","sourceCodeStart":286,"sourceCodeEnd":322,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/plugin/sandbox.go#L286-L322","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nglobalThis.siyuan = JSON.stringify({server:{}}) // oops: string\n// after\nglobalThis.siyuan = { server: {} }","handlingStrategy":"type-guard","validationCode":"// Reject non-object intermediate before the kernel traverses it.\nfunction assertObjectAt(path: string[]): void {\n  let cur: any = globalThis\n  for (const k of path) {\n    cur = cur[k]\n    if (typeof cur !== 'object' || cur === null || Array.isArray(cur)) {\n      throw new Error(`path ${path.join('.')}: expected object at ${k}`)\n    }\n  }\n}","typeGuard":"function isPlainObject(v: unknown): v is Record<string, unknown> {\n  return typeof v === 'object' && v !== null && !Array.isArray(v)\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["plugin","goja","javascript-runtime","type-mismatch","sandbox"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}