{"record":{"id":"b89b8895d81eb0a4","repo":"siyuan-note/siyuan","slug":"panic-during-lifecycle-hook-invocation-v","errorCode":null,"errorMessage":"panic during lifecycle hook invocation: %v","messagePattern":"panic during lifecycle hook invocation: (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/plugin/plugin.go","lineNumber":850,"sourceCode":"\tp.watcherMu.Unlock()\n\tclose(done)\n}\n\nfunc storageWatchOperations(event fsnotify.Event) (ret []string) {\n\tfor _, operation := range []fsnotify.Op{fsnotify.Create, fsnotify.Write, fsnotify.Rename, fsnotify.Remove} {\n\t\tif event.Has(operation) {\n\t\t\tret = append(ret, operation.String())\n\t\t}\n\t}\n\treturn\n}\n\n// invokeHook calls a lifecycle hook (e.g. onload) if it exists, awaiting if it returns a Promise.\nfunc (p *KernelPlugin) invokeHook(name string) {\n\tvar err error\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\terr = fmt.Errorf(\"panic during lifecycle hook invocation: %v\", r)\n\t\t}\n\n\t\tif err != nil {\n\t\t\tlogging.LogErrorf(\"[plugin:%s] lifecycle hook [%q] error: %v\", p.Name, name, err)\n\t\t}\n\t}()\n\n\tdone := make(chan TaskResult, 1)\n\n\trunErr := p.worker.Run(func(rt *goja.Runtime) (_ any, err error) {\n\t\tlifecycle, err := getJsContextValue(rt, []any{\"siyuan\", \"plugin\", \"lifecycle\"})\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tif lifecycle == nil {\n\t\t\terr = fmt.Errorf(\"globalThis.siyuan.plugin.lifecycle not found\")\n\t\t\treturn\n\t\t}","sourceCodeStart":832,"sourceCodeEnd":868,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/plugin/plugin.go#L832-L868","documentation":"invokeHook recovers a panic thrown while calling a plugin lifecycle hook (onload, onunload, etc.) inside the goja runtime. The panic value is wrapped with this message and logged via logging.LogErrorf as \"lifecycle hook [%q] error\"; it is not propagated, so the plugin process survives but the hook did not complete.","triggerScenarios":"A lifecycle hook (or code it calls synchronously) panics inside the goja runtime - e.g. calling a method on a nil kernel-provided object, a runtime.GoRundown during shutdown, or a JS callback that re-enters the runtime.","commonSituations":"Plugin onload accessing kernel APIs before initialization; onunload touching already-freed sockets; hooks that throw during plugin reload/disable cycles.","solutions":["Read the log line `lifecycle hook [%q] error: %v` to see the panic value and hook name","Fix the plugin's hook implementation - typically a nil dereference or call into an already-closed runtime","Wrap risky sections of the plugin's hook in try/catch (JS) so panics do not escape into goja","Check kernel-side nil guards in objects passed to the hook before invocation"],"exampleFix":"// plugin JS (before)\nonload() { this.data.items.forEach(...) } // this.data undefined -> panic\n// after\nonload() {\n  if (!this.data || !Array.isArray(this.data.items)) return\n  this.data.items.forEach(...)\n}","handlingStrategy":"try-catch","validationCode":"// plugin JS: pre-flight check before kernel invokes the hook\nif (typeof this.onload !== 'function' || typeof this.onunload !== 'function') {\n  throw new Error('lifecycle hooks missing')\n}","typeGuard":null,"tryCatchPattern":"p.invokeHook(\"onload\") // panics are recovered inside invokeHook\n// caller side: treat log line `lifecycle hook [\"onload\"] error: ...` as plugin fault,\n// optionally disable the plugin after repeated hook failures","preventionTips":["Wrap plugin hook bodies in try/catch on the JS side","Null-check all kernel-provided objects before use inside hooks","Avoid touching freed resources (sockets, runtime) in onunload","Test plugin load/unload cycles repeatedly to surface teardown races"],"tags":["plugin","panic","lifecycle","goja"],"backgroundTag":"recovered-panic","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"}