{"record":{"id":"c3466ec13c093746","repo":"siyuan-note/siyuan","slug":"path-v-value-is-s","errorCode":null,"errorMessage":"path %v: value is %s","messagePattern":"path (.+?): value is (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/plugin/sandbox.go","lineNumber":298,"sourceCode":"\tif err := ObjectSetDataMethods(p, rt, obj, data); err != nil {\n\t\treturn nil, err\n\t}\n\treturn obj, nil\n}\n\n// 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)","sourceCodeStart":280,"sourceCodeEnd":316,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/plugin/sandbox.go#L280-L316","documentation":"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\").","triggerScenarios":"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.","commonSituations":"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.","solutions":["Confirm every scope/requestType the kernel will query is populated in globalThis.siyuan.server[scope][requestType] as a non-null object.","Guard the plugin with typeof checks before assignment so partial registration cannot leave undefined holes.","Match the plugin's registration to the kernel's AccessScope/RequestType string constants exactly (case-sensitive)."],"exampleFix":"// before\nglobalThis.siyuan.server = { network: { /* missing other scope */ } }\n// after\nconst server = globalThis.siyuan.server || (globalThis.siyuan.server = {})\nserver.network = server.network || {}\nserver.network.beforeRequest = { handler: (req) => { ... } }","handlingStrategy":"validation","validationCode":"// Plugin side: ensure scope/requestType objects exist before assigning handler.\nconst server = (globalThis.siyuan.server ??= {})\nconst scope = (server['network'] ??= {})\nif (scope['beforeRequest'] == null) scope['beforeRequest'] = {}","typeGuard":"function scopeEntryExists(scope: string, rt: string): boolean {\n  const n = globalThis.siyuan?.server?.[scope]?.[rt]\n  return n !== undefined && n !== null\n}","tryCatchPattern":null,"preventionTips":["Register every scope/requestType the kernel will query; do not leave holes.","Use nullish-coalescing assignment to guarantee object creation at each level.","Match scope/requestType strings to the kernel's AccessScope/RequestType constants (case-sensitive)."],"tags":["plugin","goja","javascript-runtime","sandbox","null-check"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}