siyuan-note/siyuan · error

globalThis.siyuan.plugin.lifecycle not found

Error message

globalThis.siyuan.plugin.lifecycle not found

What it means

During hook invocation the kernel resolves the path globalThis.siyuan.plugin.lifecycle inside the plugin's goja runtime via getJsContextValue. If the value is undefined/null the runtime is told to run the hook anyway and fails fast with this error: the plugin never installed its lifecycle object, so no hooks can be dispatched.

Source

Thrown at kernel/plugin/plugin.go:866

	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
		}

		pluginObj := lifecycle.ToObject(rt)
		if pluginObj == nil {
			err = fmt.Errorf("globalThis.siyuan.plugin.lifecycle is not an object")
			return
		}

		hookValue := pluginObj.Get(name)
		hook, ok := goja.AssertFunction(hookValue)
		if !ok {
			err = fmt.Errorf("globalThis.siyuan.plugin.lifecycle.%s not bound to a function", name)
			return
		}

		invokeFunction(func(_ *goja.Runtime, result *CallResult) {
			done <- *result.TaskResult()

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Check earlier logs for JS load/compile errors from the plugin entry - fix the root script failure first
  2. Ensure the plugin assigns globalThis.siyuan.plugin.lifecycle (or registers via the official plugin scaffold) before hooks fire
  3. Reinstall/rebuild the plugin so its entry file is complete and compatible with the kernel API
  4. Verify the entry script actually ran: add a console log at the top of the plugin entry and check plugin logs

Example fix

// plugin entry (before)
export default class { onload() {} } // never attached to siyuan.plugin.lifecycle
// after
globalThis.siyuan = globalThis.siyuan || {}
globalThis.siyuan.plugin = globalThis.siyuan.plugin || {}
globalThis.siyuan.plugin.lifecycle = new (class { onload() {} onunload() {} })()
Defensive patterns

Strategy: validation

Validate before calling

// plugin JS entry must end with something like:
if (!globalThis.siyuan?.plugin?.lifecycle) {
  throw new Error('entry failed to install siyuan.plugin.lifecycle')
}

Type guard

func lifecycleInstalled(rt *goja.Runtime) bool {
  v, err := getJsContextValue(rt, []any{"siyuan", "plugin", "lifecycle"})
  return err == nil && v != nil
}

Try / catch

if err := dispatchHook("onload"); err != nil {
  if strings.Contains(err.Error(), "lifecycle not found") {
    logging.LogErrorf("plugin entry did not register lifecycle; disabling plugin")
    // disable or quarantine the plugin
  }
}

Prevention

When it happens

Trigger: Calling invokeHook/lifecycle dispatch when the plugin's JS entry script did not (successfully) assign globalThis.siyuan.plugin.lifecycle - e.g. the script errored mid-execution or was never registered.

Common situations: A plugin entry file that failed to load earlier (syntax error, missing export) leaving the context unset; a plugin written for a different host (no `siyuan` global); truncated or corrupted plugin dist files.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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