{"record":{"id":"a4f7675855f4e542","repo":"siyuan-note/siyuan","slug":"goja-panic-during-dispatchevent-v","errorCode":null,"errorMessage":"goja panic during dispatchEvent: %v","messagePattern":"goja panic during dispatchEvent: (.+?)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/plugin/sandbox.go","lineNumber":328,"sourceCode":"\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}\n\n// dispatchEvent calls the globalThis.siyuan.event.on hook with the given event object.\nfunc dispatchEvent(p *KernelPlugin, rt *goja.Runtime, e any) (async bool, err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\terr = fmt.Errorf(\"goja panic during dispatchEvent: %v\", r)\n\t\t}\n\t}()\n\n\tevent, err := getJsContextValue(rt, []any{\"siyuan\", \"event\"})\n\tif err != nil {\n\t\treturn\n\t}\n\tif event == nil {\n\t\terr = fmt.Errorf(\"globalThis.siyuan.event not found\")\n\t\treturn\n\t}\n\tif goja.IsUndefined(event) || goja.IsNull(event) {\n\t\terr = fmt.Errorf(\"globalThis.siyuan.event is %s\", event.String())\n\t\treturn\n\t}\n\n\teventObj := event.ToObject(rt)\n\tif eventObj == nil {","sourceCodeStart":310,"sourceCodeEnd":346,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/plugin/sandbox.go#L310-L346","documentation":"dispatchEvent installs a Go recover() so that a panic raised inside the goja runtime while calling the plugin's globalThis.siyuan.event.on hook is converted into this error instead of crashing the kernel. The %v carries the recovered panic value, which is usually a *goja.Object exception or a goja-thrown JS error.","triggerScenarios":"The plugin's siyuan.event.on handler (or code it calls) throws an uncaught JS exception or goja panics while dispatchEvent invokes handler(event, eventJs).","commonSituations":"Plugin event handlers with bugs: accessing properties of undefined, calling a non-function hook, infinite recursion, or a handler that itself calls kernel APIs which throw.","solutions":["Read the %v detail in the error to identify the JS exception, and fix the throwing code in the plugin's siyuan.event.on handler","Wrap the handler body in try/catch inside the plugin so exceptions are logged instead of propagating into goja","Verify siyuan.event.on is a function (typeof siyuan.event.on === 'function') before registering event listeners"],"exampleFix":"// before (plugin JS)\nsiyuan.event.on(eventName, e => processEvent(e.detail.id));\n// after\nsiyuan.event.on(eventName, e => { try { processEvent(e && e.detail && e.detail.id); } catch (err) { console.error(err); } });","handlingStrategy":"try-catch","validationCode":"console.assert(typeof siyuan !== 'undefined' && siyuan.event && typeof siyuan.event.on === 'function', 'event hook missing');","typeGuard":"const hasEventHook = () => typeof siyuan !== 'undefined' && siyuan.event != null && typeof siyuan.event.on === 'function';","tryCatchPattern":"try { const r = dispatchEvent(p, rt, e); if (r.err != nil) { log.Warnf(\"event dispatch failed: %v\", r.err) } } catch (err) { log.Warnf(\"event dispatch panicked: %v\", err) }","preventionTips":["Wrap every plugin event handler body in try/catch","Never throw from top-level handler code; log and return instead","Test handlers with malformed/missing event payloads before release"],"tags":["goja","javascript","panic","events"],"backgroundTag":"internal-invariant-violation","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}