siyuan-note/siyuan · error

panic during lifecycle hook invocation: %v

Error message

panic during lifecycle hook invocation: %v

What it means

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.

Source

Thrown at kernel/plugin/plugin.go:850

	p.watcherMu.Unlock()
	close(done)
}

func storageWatchOperations(event fsnotify.Event) (ret []string) {
	for _, operation := range []fsnotify.Op{fsnotify.Create, fsnotify.Write, fsnotify.Rename, fsnotify.Remove} {
		if event.Has(operation) {
			ret = append(ret, operation.String())
		}
	}
	return
}

// invokeHook calls a lifecycle hook (e.g. onload) if it exists, awaiting if it returns a Promise.
func (p *KernelPlugin) invokeHook(name string) {
	var err error
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("panic during lifecycle hook invocation: %v", r)
		}

		if err != nil {
			logging.LogErrorf("[plugin:%s] lifecycle hook [%q] error: %v", p.Name, name, err)
		}
	}()

	done := make(chan TaskResult, 1)

	runErr := p.worker.Run(func(rt *goja.Runtime) (_ any, err error) {
		lifecycle, err := getJsContextValue(rt, []any{"siyuan", "plugin", "lifecycle"})
		if err != nil {
			return
		}
		if lifecycle == nil {
			err = fmt.Errorf("globalThis.siyuan.plugin.lifecycle not found")
			return
		}

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Read the log line `lifecycle hook [%q] error: %v` to see the panic value and hook name
  2. Fix the plugin's hook implementation - typically a nil dereference or call into an already-closed runtime
  3. Wrap risky sections of the plugin's hook in try/catch (JS) so panics do not escape into goja
  4. Check kernel-side nil guards in objects passed to the hook before invocation

Example fix

// plugin JS (before)
onload() { this.data.items.forEach(...) } // this.data undefined -> panic
// after
onload() {
  if (!this.data || !Array.isArray(this.data.items)) return
  this.data.items.forEach(...)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// plugin JS: pre-flight check before kernel invokes the hook
if (typeof this.onload !== 'function' || typeof this.onunload !== 'function') {
  throw new Error('lifecycle hooks missing')
}

Try / catch

p.invokeHook("onload") // panics are recovered inside invokeHook
// caller side: treat log line `lifecycle hook ["onload"] error: ...` as plugin fault,
// optionally disable the plugin after repeated hook failures

Prevention

When it happens

Trigger: 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.

Common situations: Plugin onload accessing kernel APIs before initialization; onunload touching already-freed sockets; hooks that throw during plugin reload/disable cycles.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/b89b8895d81eb0a4. Report an issue: GitHub.