openclaw - 💡(How to fix) Fix Bug: Plugin lifecycle hooks (api.on) never fire due to initialization timing [4 comments, 3 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#52810Fetched 2026-04-05 15:08:31
View on GitHub
Comments
4
Participants
3
Timeline
4
Reactions
0
Timeline (top)
commented ×4

Plugin lifecycle hooks registered via api.on() are never invoked during agent interactions, despite successful registration and no error messages.

Error Message

Plugin lifecycle hooks registered via api.on() are never invoked during agent interactions, despite successful registration and no error messages.

Root Cause

After examining OpenClaw source code, I identified a timing issue in the plugin loading sequence:

// From compact-D3emcZgv.js
function loadOpenClawPlugins(options = {}) {
  const { registry, createApi } = createPluginRegistry({...});
  // ...
  activatePluginRegistry(registry, cacheKey);  // ← initializeGlobalHookRunner() called here
}

function activatePluginRegistry(registry, cacheKey) {
  setActivePluginRegistry(registry, cacheKey);
  initializeGlobalHookRunner(registry);  // ← registry.typedHooks is EMPTY at this point
}

Sequence of events:

  1. createPluginRegistry() creates empty registry with typedHooks = []
  2. activatePluginRegistry()initializeGlobalHookRunner() (registry still empty)
  3. Plugin register() functions are called
  4. api.on()registerTypedHook()registry.typedHooks.push() (TOO LATE!)
  5. hookRunner.hasHooks() checks the registry snapshot from step 2 (always returns false)

Evidence:

  • No "hook runner initialized" log (because registry.hooks.length === 0)
  • registry.typedHooks is populated AFTER globalHookRunner is created
  • hasHooks() uses the empty registry

Fix Action

Workaround

Currently, the only workaround is to use MCP tools directly instead of lifecycle hooks, requiring manual invocation by users.

Code Example

export default function register(api) {
     api.on("before_agent_start", async (event, ctx) => {
       console.log("[plugin] before_agent_start hook fired");
       return { prependContext: "test" };
     });
     
     api.on("agent_end", async (event, ctx) => {
       console.log("[plugin] agent_end hook fired");
     });
   }

---

{
     "plugins": {
       "allow": ["my-plugin"],
       "entries": {
         "my-plugin": { "enabled": true }
       }
     }
   }

---

[my-plugin] Plugin loaded
[my-plugin] Lifecycle hooks registered (before_agent_start + agent_end)

---

// From compact-D3emcZgv.js
function loadOpenClawPlugins(options = {}) {
  const { registry, createApi } = createPluginRegistry({...});
  // ...
  activatePluginRegistry(registry, cacheKey);  // ← initializeGlobalHookRunner() called here
}

function activatePluginRegistry(registry, cacheKey) {
  setActivePluginRegistry(registry, cacheKey);
  initializeGlobalHookRunner(registry);  // ← registry.typedHooks is EMPTY at this point
}
RAW_BUFFERClick to expand / collapse

Bug: Plugin lifecycle hooks (api.on) never fire due to initialization timing

Summary

Plugin lifecycle hooks registered via api.on() are never invoked during agent interactions, despite successful registration and no error messages.

Environment

  • OpenClaw version: v2026.3.13 (61d171a)
  • Node.js: v22.22.1
  • OS: Ubuntu 24.04
  • Plugin: lobster-press v4.0.42

Steps to Reproduce

  1. Create a plugin with lifecycle hooks:

    export default function register(api) {
      api.on("before_agent_start", async (event, ctx) => {
        console.log("[plugin] before_agent_start hook fired");
        return { prependContext: "test" };
      });
      
      api.on("agent_end", async (event, ctx) => {
        console.log("[plugin] agent_end hook fired");
      });
    }
  2. Configure plugin in openclaw.json:

    {
      "plugins": {
        "allow": ["my-plugin"],
        "entries": {
          "my-plugin": { "enabled": true }
        }
      }
    }
  3. Start OpenClaw Gateway

  4. Send messages to agent

  5. Check logs

Expected Behavior

  • [plugin] before_agent_start hook fired should appear in logs
  • [plugin] agent_end hook fired should appear after agent completes
  • Hooks should be called for every agent interaction

Actual Behavior

Logs show:

[my-plugin] Plugin loaded
[my-plugin] Lifecycle hooks registered (before_agent_start + agent_end)

Missing:

  • No "hook fired" logs ever appear
  • No "hook runner initialized" logs
  • hookRunner.hasHooks("agent_end") returns false
  • Database operations in hooks never execute

Root Cause Analysis

After examining OpenClaw source code, I identified a timing issue in the plugin loading sequence:

// From compact-D3emcZgv.js
function loadOpenClawPlugins(options = {}) {
  const { registry, createApi } = createPluginRegistry({...});
  // ...
  activatePluginRegistry(registry, cacheKey);  // ← initializeGlobalHookRunner() called here
}

function activatePluginRegistry(registry, cacheKey) {
  setActivePluginRegistry(registry, cacheKey);
  initializeGlobalHookRunner(registry);  // ← registry.typedHooks is EMPTY at this point
}

Sequence of events:

  1. createPluginRegistry() creates empty registry with typedHooks = []
  2. activatePluginRegistry()initializeGlobalHookRunner() (registry still empty)
  3. Plugin register() functions are called
  4. api.on()registerTypedHook()registry.typedHooks.push() (TOO LATE!)
  5. hookRunner.hasHooks() checks the registry snapshot from step 2 (always returns false)

Evidence:

  • No "hook runner initialized" log (because registry.hooks.length === 0)
  • registry.typedHooks is populated AFTER globalHookRunner is created
  • hasHooks() uses the empty registry

Why It Matters

This affects any plugin trying to use lifecycle hooks for:

  • Memory management (capture/recall at session boundaries)
  • Context injection (prepending context before agent starts)
  • Audit logging (tracking agent interactions)
  • Resource cleanup (releasing resources after agent ends)

Workaround

Currently, the only workaround is to use MCP tools directly instead of lifecycle hooks, requiring manual invocation by users.

Proposed Fix

Two possible approaches:

Option 1: Defer hookRunner initialization Move initializeGlobalHookRunner() to after all plugins' register() functions complete.

Option 2: Use live registry reference Make hookRunner.hasHooks() check the live registry.typedHooks instead of a snapshot.

Related Information

  • pluginHookNameSet includes "agent_end" (hook name is valid)
  • registerTypedHook() works correctly (no "unknown typed hook" warnings)
  • memory-lancedb plugin uses identical api.on() pattern

Test Case

I can provide access to a test machine (192.168.11.219) with the failing configuration for debugging.

extent analysis

Fix Plan

To resolve the issue with plugin lifecycle hooks not firing due to initialization timing, we will implement Option 1: Defer hookRunner initialization. This involves moving the initializeGlobalHookRunner() call to after all plugins' register() functions have completed.

Step-by-Step Solution:

  1. Modify the loadOpenClawPlugins function to defer the initialization of the global hook runner.
  2. Introduce a new function that will be responsible for initializing the global hook runner after all plugins have been registered.
  3. Call this new function after the loop that registers all plugins.

Example Code:

// Modified loadOpenClawPlugins function
function loadOpenClawPlugins(options = {}) {
  const { registry, createApi } = createPluginRegistry({...});
  // ...
  // Defer initializeGlobalHookRunner call
  registerPlugins(registry, createApi, options).then(() => {
    activatePluginRegistry(registry, cacheKey);
  });
}

// New function to register plugins and then activate the registry
function registerPlugins(registry, createApi, options) {
  return Promise.all(
    Object.keys(options.plugins.entries).map(pluginName => {
      const plugin = options.plugins.entries[pluginName];
      if (plugin.enabled) {
        // Register plugin
        const api = createApi(pluginName);
        require(pluginName)(api);
      }
    })
  ).then(() => {
    // After all plugins are registered, activate the registry
    // This will call initializeGlobalHookRunner with the updated registry
  });
}

// Modified activatePluginRegistry function to ensure initializeGlobalHookRunner
// is called after all plugins have been registered
function activatePluginRegistry(registry, cacheKey) {
  setActivePluginRegistry(registry, cacheKey);
  initializeGlobalHookRunner(registry); // Now registry.typedHooks is populated
}

Verification

To verify that the fix worked:

  • Start OpenClaw Gateway with the modified code.
  • Send messages to the agent.
  • Check the logs for the presence of [plugin] before_agent_start hook fired and [plugin] agent_end hook fired messages.
  • Confirm that database operations within the hooks are executed as expected.

Extra Tips

  • Ensure that all plugins are properly registered and enabled in the openclaw.json configuration file.
  • Test the fix with multiple plugins to ensure that the issue is resolved for all of them.
  • Consider adding logging or debugging statements to verify the correct execution of the initializeGlobalHookRunner function and the population of registry.typedHooks.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - 💡(How to fix) Fix Bug: Plugin lifecycle hooks (api.on) never fire due to initialization timing [4 comments, 3 participants]