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
- Inspect what the plugin assigns to globalThis.siyuan.plugin.lifecycle; make it a plain object or class instance
- Search the plugin bundle for reassignments/overwrites of the lifecycle slot during init
- Ensure asynchronous init does not null the slot before the kernel invokes hooks
- 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
- Assign an object (or class instance) to globalThis.siyuan.plugin.lifecycle, never a primitive or null
- Search bundled plugin code for later reassignments of the lifecycle slot
- Avoid async init paths that can null the slot before hooks fire
- Lint plugin entry code for `lifecycle =` assignments
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
- panic during lifecycle hook invocation: %v
- globalThis.siyuan.plugin.lifecycle not found
- globalThis.siyuan.plugin.lifecycle.%s not bound to a functio
- siyuan.server[%s][%s].handler is not a function
- injectAgent: %v
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/26fe95f2cd34aaf1.
Report an issue: GitHub.