-
-
Notifications
You must be signed in to change notification settings - Fork 69.7k
Internal hook message:sent never fires due to split handlers Map between bundle chunks #30624
Description
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
- Create a hook at
~/.openclaw/hooks/my-hook/handler.tswith metadataevents: ["message", "agent"] - Handle
event.type === "message" && event.action === "sent"in the handler - Send a message through a Feishu session and trigger a reply from the agent
- Observe that the
message:senthandler branch is never reached (onlymessage:receivedfires)
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:sentfires in the registered hook handler every time the agent successfully delivers a replyagent:endfires 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:
- Preferred: Extract the
handlersMap 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. - 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.shThis is fragile and should not be necessary once the hook system works correctly.