siyuan-note/siyuan · error

globalThis.siyuan.plugin.lifecycle is not an object

Error message

globalThis.siyuan.plugin.lifecycle is not an object

What it means

After resolving globalThis.siyuan.plugin.lifecycle, the kernel calls ToObject(rt); if goja yields nil the value is not a usable object, and hook dispatch aborts with this error. This guards against the lifecycle slot being a non-object (e.g. null, a number, a boolean).

Source

Thrown at kernel/plugin/plugin.go:872

			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()
		}, rt, true, hook, lifecycle)
		return
	}, func(_ *goja.Runtime, _ any, err error) {
		if err != nil {
			done <- TaskResult{err: err}
		}

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Inspect what the plugin assigns to globalThis.siyuan.plugin.lifecycle; make it a plain object or class instance
  2. Search the plugin bundle for reassignments/overwrites of the lifecycle slot during init
  3. Ensure asynchronous init does not null the slot before the kernel invokes hooks
  4. Use the official plugin template so the lifecycle object is constructed correctly

Example fix

// before
globalThis.siyuan.plugin.lifecycle = null // reset in some init path
// after
globalThis.siyuan.plugin.lifecycle = globalThis.siyuan.plugin.lifecycle || new PluginLifecycle()
Defensive patterns

Strategy: type-guard

Validate before calling

// plugin JS before handing off
const lc = globalThis.siyuan?.plugin?.lifecycle
if (typeof lc !== 'object' || lc === null) {
  throw new Error('siyuan.plugin.lifecycle must be an object')
}

Type guard

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

Try / catch

if err := dispatchHook(name); err != nil {
  if strings.Contains(err.Error(), "lifecycle is not an object") {
    logging.LogErrorf("plugin %s assigned non-object to lifecycle slot", p.Name)
  }
}

Prevention

When it happens

Trigger: Plugin code assigning a non-object to globalThis.siyuan.plugin.lifecycle (e.g. `null`, `undefined` assigned after check, a primitive), so lifecycle.ToObject(rt) returns nil.

Common situations: Typo like `siyuan.plugin.lifecycle = null` during an init reset; minified/bundled plugin overwriting the slot; a plugin that exports a class but never instantiates it into the slot.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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