Skip to content

Internal hook message:sent never fires due to split handlers Map between bundle chunks #30624

@ou524u

Description

@ou524u

Summary

Internal hooks registered for the message event type (specifically action: "sent") never fire, even when the hook is correctly registered and the delivery path does call triggerInternalHook("message", "sent", ...). The root cause is that registerInternalHook and triggerInternalHook operate on two separate handlers Map instances in the production bundle.

Environment

  • OpenClaw version: 2026.2.25
  • Channel: Feishu (DM)
  • Hook metadata: { "events": ["message", "agent"] }

Steps to Reproduce

  1. Create a hook at ~/.openclaw/hooks/my-hook/handler.ts with metadata events: ["message", "agent"]
  2. Handle event.type === "message" && event.action === "sent" in the handler
  3. Send a message through a Feishu session and trigger a reply from the agent
  4. Observe that the message:sent handler branch is never reached (only message:received fires)

Add a console.log at the top of your handler and filter gateway.log — you will see type=message action=received events, but never type=message action=sent.

Root Cause Analysis

The internal hook system (handlers Map, registerInternalHook, triggerInternalHook) is defined in multiple bundle chunks:

Chunk Role
entry.js Main process; loadInternalHooks() calls registerInternalHook() here
subsystem-Bqlcd6-a.js Imported by deliver-C3bAT7D8.js; triggerInternalHook() fires here

Both files contain this line independently:

const handlers = /* @__PURE__ */ new Map();

Because each chunk instantiates its own handlers Map, hooks registered in entry.js's Map are invisible to triggerInternalHook in subsystem-Bqlcd6-a.js's Map — and vice versa.

Registration path:

gateway startup
  → loadInternalHooks()          [entry.js]
  → registerInternalHook()       [entry.js / Map A]

Trigger path:

agent reply
  → deliverOutboundPayloadsCore  [deliver-C3bAT7D8.js]
  → triggerInternalHook()        [subsystem-Bqlcd6-a.js / Map B]  ← empty, no handlers registered

The same analysis applies to agent:end — no code path in the current bundle calls triggerInternalHook("agent", "end", ...) at all, so that event is effectively unimplemented.

Expected Behavior

  • message:sent fires in the registered hook handler every time the agent successfully delivers a reply
  • agent:end fires when the agent finishes processing a turn (useful for post-reply side effects such as adding a status reaction to the originating message)

Suggested Fix

Ensure registerInternalHook and triggerInternalHook share a single module-level singleton across all chunks. Two options:

  1. Preferred: Extract the handlers Map into a dedicated singleton module (e.g., hook-registry.ts) and import from it everywhere — the bundler will then treat it as a single shared instance.
  2. Alternative: Use a process-level global (globalThis.__ocHookHandlers) as the backing store, which survives chunk boundaries by design.

Workaround

Currently working around this by manually invoking a shell script after each reply:

~/.openclaw/hooks/feishu-status-reactions/add-done-reaction.sh

This is fragile and should not be necessary once the hook system works correctly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    staleMarked as stale due to inactivity

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions