Summary
Plugin hooks registered via api.on("before_reset", handler) never fire when the /new or /reset command is processed. The handler registers successfully (visible in openclaw plugins list as "loaded"), but hookRunner.hasHooks("before_reset") returns false at the call site.
Root Cause
The build system (Rolldown) creates separate module bundles that each have their own globalHookRunner singleton:
pi-embedded-CAmQsy9D.js imports getGlobalHookRunner from deliver-Vheuo8GO.js
subagent-registry-Bdm_X-N1.js imports getGlobalHookRunner from deliver-Day3efJ9.js
These are different files (43KB vs 45KB) with separate module-level globalHookRunner variables. When a plugin calls api.on("before_reset", handler), the typed hook is registered in one module's registry.typedHooks. But the /new command handler calls getGlobalHookRunner() from a different module's scope, where the hook runner was either never initialized or initialized with a different registry.
The internal hooks system (registerInternalHook / triggerInternalHook) works correctly because it shares state across module boundaries.
Reproduction
- Create a workspace plugin that uses
api.on("before_reset", handler) with a console.log in the handler
- Run
openclaw plugins list — plugin shows as "loaded"
- Run
/new in any channel
- Check logs — the handler never fires, no console output
Expected Behavior
api.on("before_reset") handlers should fire when /new or /reset is processed, regardless of which module bundle processes the command.
Workaround
Use workspace hooks (internal hooks system) instead of plugin typed hooks for command:new / command:reset events. This works because triggerInternalHook shares state across module boundaries.
Note: before_compaction and after_compaction have no internal hook equivalent, so plugins cannot reliably hook into compaction events.
Environment
- OpenClaw version: 2026.2.21 (npm)
- Node: v25.5.0
- OS: macOS (arm64)
- Build files examined:
pi-embedded-CAmQsy9D.js, subagent-registry-Bdm_X-N1.js, deliver-Vheuo8GO.js, deliver-Day3efJ9.js
Summary
Plugin hooks registered via
api.on("before_reset", handler)never fire when the/newor/resetcommand is processed. The handler registers successfully (visible inopenclaw plugins listas "loaded"), buthookRunner.hasHooks("before_reset")returnsfalseat the call site.Root Cause
The build system (Rolldown) creates separate module bundles that each have their own
globalHookRunnersingleton:pi-embedded-CAmQsy9D.jsimportsgetGlobalHookRunnerfromdeliver-Vheuo8GO.jssubagent-registry-Bdm_X-N1.jsimportsgetGlobalHookRunnerfromdeliver-Day3efJ9.jsThese are different files (43KB vs 45KB) with separate module-level
globalHookRunnervariables. When a plugin callsapi.on("before_reset", handler), the typed hook is registered in one module'sregistry.typedHooks. But the/newcommand handler callsgetGlobalHookRunner()from a different module's scope, where the hook runner was either never initialized or initialized with a different registry.The internal hooks system (
registerInternalHook/triggerInternalHook) works correctly because it shares state across module boundaries.Reproduction
api.on("before_reset", handler)with aconsole.login the handleropenclaw plugins list— plugin shows as "loaded"/newin any channelExpected Behavior
api.on("before_reset")handlers should fire when/newor/resetis processed, regardless of which module bundle processes the command.Workaround
Use workspace hooks (internal hooks system) instead of plugin typed hooks for
command:new/command:resetevents. This works becausetriggerInternalHookshares state across module boundaries.Note:
before_compactionandafter_compactionhave no internal hook equivalent, so plugins cannot reliably hook into compaction events.Environment
pi-embedded-CAmQsy9D.js,subagent-registry-Bdm_X-N1.js,deliver-Vheuo8GO.js,deliver-Day3efJ9.js