openclaw - 💡(How to fix) Fix Hook: before_route_inbound_message — pre-routing interception for channel bridging/proxying

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…

Fix Action

Fix / Workaround

Dispatch Pipeline Placement

  1. src/auto-reply/dispatch-dispatcher.ts: Add the hook call before resolveSessionStoreEntry resolves the session key
  2. src/plugins/hooks.ts: Register as a new modifying hook type with timeout handling (similar to message_sending)
  3. src/plugins/types.ts: Add type definitions
  4. Plugin SDK types: Export the event/context/result types
  • Fork implementation modifies recordInboundSession in src/channels/session.ts to rewrite canonicalSessionKey
  • Current workaround requires code changes to stock OpenClaw

Code Example

interface BeforeRouteInboundMessageEvent {
  channel: string;              // e.g. "discord"
  accountId: string;            // account ID the message came through
  conversationId: string;       // raw channel-specific conversation ID
  parentConversationId?: string;
  chatType: "direct" | "group" | "channel";
  senderId: string;             // raw channel-specific sender ID
  body: string;                 // message text content
  targetSessionKey?: string;    // already-resolved session key (from CommandTargetSessionKey or ctx.SessionKey)
  metadata?: Record<string, unknown>;
}

interface BeforeRouteInboundMessageContext {
  config: OpenClawConfig;
  logger: Logger;
  sessionStore: SessionStore;
}

type BeforeRouteInboundMessageResult =
  | { handled: true; redirectSessionKey: string }   // Route to a different session
  | { handled: true; suppressDelivery: true }       // Drop the message
  | { handled: false }                              // Let stock routing proceed
  | null;                                           // Same as declined

---

export default definePlugin({
  id: "channel-bridge",
  hooks: {
    before_route_inbound_message: async (event, ctx) => {
      const proxyConfig = ctx.config.session?.channelBridge?.proxies;
      if (!proxyConfig) return null;

      const key = `${event.channel}:${event.conversationId}`;
      const binding = proxyConfig[key];
      if (!binding) return null;

      return { handled: true, redirectSessionKey: binding };
    }
  }
});

---

Message arrives from channel
Resolve channel account & conversation metadata
[NEW: before_route_inbound_message fires here]
  └── plugins can redirect, suppress, or let through
Resolve/create canonicalSessionKey (stock routing)
message_received hook fires (existing behavior unchanged)
Agent processes message in session
RAW_BUFFERClick to expand / collapse

Problem Statement

The current plugin hook system lacks a pre-routing inbound message hook. All existing hooks (message_received, message_sending, etc.) fire after the routing decision has already been made and the message assigned to its canonical session.

This creates an architectural gap for legitimate use cases like:

  • Channel bridging/proxying: Redirect guild messages from a Discord channel session to the agent's main session (and vice versa) before stock auto-routes them
  • Custom routing plugins: Implement session binding, message interception, or delivery suppression based on plugin logic
  • Multi-session consolidation: Prevent isolated channel sessions from being created when a different session should handle the message

Current Behavior

  1. Message arrives from Discord guild #my-bots
  2. OpenClaw resolves canonicalSessionKey → creates/uses channel session (agent:main:discord:channel:1466895086234243144)
  3. message_received hook fires within that channel session context — too late to redirect
  4. Agent auto-replies in the wrong session, completely isolated from the main conversation

The only way to solve this today is by forking OpenClaw and modifying recordInboundSession directly.

Proposed Hook: before_route_inbound_message

Signature

interface BeforeRouteInboundMessageEvent {
  channel: string;              // e.g. "discord"
  accountId: string;            // account ID the message came through
  conversationId: string;       // raw channel-specific conversation ID
  parentConversationId?: string;
  chatType: "direct" | "group" | "channel";
  senderId: string;             // raw channel-specific sender ID
  body: string;                 // message text content
  targetSessionKey?: string;    // already-resolved session key (from CommandTargetSessionKey or ctx.SessionKey)
  metadata?: Record<string, unknown>;
}

interface BeforeRouteInboundMessageContext {
  config: OpenClawConfig;
  logger: Logger;
  sessionStore: SessionStore;
}

type BeforeRouteInboundMessageResult =
  | { handled: true; redirectSessionKey: string }   // Route to a different session
  | { handled: true; suppressDelivery: true }       // Drop the message
  | { handled: false }                              // Let stock routing proceed
  | null;                                           // Same as declined

Hook Type: Modifying (first-writer wins)

  • If no plugin handles it → stock routing proceeds normally
  • If a plugin returns { handled: true; redirectSessionKey: "agent:main:main" } → message goes to main session
  • If a plugin returns { handled: true; suppressDelivery: true } → message is dropped

Example Plugin Usage

export default definePlugin({
  id: "channel-bridge",
  hooks: {
    before_route_inbound_message: async (event, ctx) => {
      const proxyConfig = ctx.config.session?.channelBridge?.proxies;
      if (!proxyConfig) return null;

      const key = `${event.channel}:${event.conversationId}`;
      const binding = proxyConfig[key];
      if (!binding) return null;

      return { handled: true, redirectSessionKey: binding };
    }
  }
});

Dispatch Pipeline Placement

Message arrives from channel
Resolve channel account & conversation metadata
[NEW: before_route_inbound_message fires here]
  └── plugins can redirect, suppress, or let through
Resolve/create canonicalSessionKey (stock routing)
message_received hook fires (existing behavior unchanged)
Agent processes message in session

Implementation Notes

  1. src/auto-reply/dispatch-dispatcher.ts: Add the hook call before resolveSessionStoreEntry resolves the session key
  2. src/plugins/hooks.ts: Register as a new modifying hook type with timeout handling (similar to message_sending)
  3. src/plugins/types.ts: Add type definitions
  4. Plugin SDK types: Export the event/context/result types

Backwards Compatibility

  • New hook name — zero impact on existing plugins
  • Existing hooks remain unchanged
  • Simply adds a new interception point before the routing decision

Other Use Cases Once This Exists

  1. Session binding: Bind specific conversations to specific sessions based on custom rules
  2. Message suppression: Drop messages from certain senders/conversations at the routing layer
  3. Multi-agent routing: Route messages to different agent instances based on content or sender
  4. Compliance/audit: Intercept all inbound messages before they enter any session for logging
  5. Custom auth gates: Implement pre-session authentication

Related

  • Fork implementation modifies recordInboundSession in src/channels/session.ts to rewrite canonicalSessionKey
  • Current workaround requires code changes to stock OpenClaw

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