openclaw - ✅(Solved) Fix Inconsistent message:sent / message_sent hook coverage across multiple outbound dispatch paths [1 pull requests, 2 comments, 2 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#50126Fetched 2026-04-08 00:58:54
View on GitHub
Comments
2
Participants
2
Timeline
6
Reactions
0
Timeline (top)
referenced ×3commented ×2cross-referenced ×1

Root Cause

Root Cause Analysis

Current Outbound Paths

PathSourcemessage:sent (internal)message_sent (plugin)
deliverOutboundPayloads (Core)src/infra/outbound/deliver.ts✅ requires sessionKey
deliverReplies (Telegram)extensions/telegram/src/bot/delivery.replies.ts❌ sessionKey not passed❌ hookRunner not passed
outbound-send-service (plugin tool)src/infra/outbound/outbound-send-service.ts✅ conditionalGap
respond callback (RPC)src/gateway/server-methods/agent.ts❌ not triggered

Key Issues

  1. deliverReplies gap: When deliverReplies is called from bot-message-dispatch.ts, sessionKeyForInternalHooks and hookRunner are not passed. Inside emitMessageSentHooks(), all conditions fail and no hooks ever fire.
  2. Architectural fragmentation: deliverReplies and deliverOutboundPayloads have overlapping functionality but do not share an abstraction. Each channel implements sending independently.
  3. plugin message_sent gap: The outbound-send-service plugin-handled path cannot trigger the plugin hook (documented gap, separate from this issue).

Reproduction Steps

  1. Enable the conversation-stream workspace hook (listens to message:sent event)
  2. Send a message to the bot via Telegram DM
  3. Bot replies with direct text (not via the message tool)
  4. Observe: the outbound reply is missing from memory/conversations/telegram__<peer>/<date>.jsonl

Impact

  • conversation-stream hook: outbound messages are missing from session history
  • Any plugin relying on message:sent / message_sent hooks: may miss delivery events

Suggested Fixes

Option A — Minimal change (recommended for quick fix)

Pass sessionKeyForInternalHooks and hookRunner when calling deliverReplies in bot-message-dispatch.ts:

// extensions/telegram/src/bot-message-dispatch.ts
await deliverReplies({
// ...existing params
sessionKeyForInternalHooks: ctxPayload.SessionKey,
hookRunner: /* obtain via runtime or global */,
});

Option B — Architectural

Introduce a unified OutboundDeliveryObserver that every channel dispatch path calls exactly once. All hooks are mounted at this layer — no longer dependent on each path manually firing hooks. This eliminates coverage gaps regardless of how many dispatch paths exist.

Relevant Files

FileRole
extensions/telegram/src/bot/delivery.replies.ts漏录位置 — Telegram sending layer
extensions/telegram/src/bot-message-dispatch.tsCalls deliverReplies
src/infra/outbound/deliver.tsReference implementation (hooks fire correctly)
src/infra/outbound/outbound-send-service.tsSeparate gap: plugin hook not triggered
workspace/hooks/conversation-stream/handler.tsAffected hook

Tags

bug, hooks, telegram, architecture

Fix Action

Fix / Workaround

Problem Description

OpenClaw currently has multiple independent outbound message dispatch paths, but hook triggering coverage is inconsistent. Some outbound messages never trigger message:sent (internal) or message_sent (plugin) hooks, causing downstream hooks (e.g. conversation-stream) to miss sent messages.

Root Cause Analysis

Current Outbound Paths

PathSourcemessage:sent (internal)message_sent (plugin)
deliverOutboundPayloads (Core)src/infra/outbound/deliver.ts✅ requires sessionKey
deliverReplies (Telegram)extensions/telegram/src/bot/delivery.replies.ts❌ sessionKey not passed❌ hookRunner not passed
outbound-send-service (plugin tool)src/infra/outbound/outbound-send-service.ts✅ conditionalGap
respond callback (RPC)src/gateway/server-methods/agent.ts❌ not triggered

Key Issues

  1. deliverReplies gap: When deliverReplies is called from bot-message-dispatch.ts, sessionKeyForInternalHooks and hookRunner are not passed. Inside emitMessageSentHooks(), all conditions fail and no hooks ever fire.
  2. Architectural fragmentation: deliverReplies and deliverOutboundPayloads have overlapping functionality but do not share an abstraction. Each channel implements sending independently.
  3. plugin message_sent gap: The outbound-send-service plugin-handled path cannot trigger the plugin hook (documented gap, separate from this issue).

Reproduction Steps

  1. Enable the conversation-stream workspace hook (listens to message:sent event)
  2. Send a message to the bot via Telegram DM
  3. Bot replies with direct text (not via the message tool)
  4. Observe: the outbound reply is missing from memory/conversations/telegram__<peer>/<date>.jsonl

Impact

  • conversation-stream hook: outbound messages are missing from session history
  • Any plugin relying on message:sent / message_sent hooks: may miss delivery events

Suggested Fixes

Option A — Minimal change (recommended for quick fix)

Pass sessionKeyForInternalHooks and hookRunner when calling deliverReplies in bot-message-dispatch.ts:

// extensions/telegram/src/bot-message-dispatch.ts
await deliverReplies({
// ...existing params
sessionKeyForInternalHooks: ctxPayload.SessionKey,
hookRunner: /* obtain via runtime or global */,
});

Option B — Architectural

Introduce a unified OutboundDeliveryObserver that every channel dispatch path calls exactly once. All hooks are mounted at this layer — no longer dependent on each path manually firing hooks. This eliminates coverage gaps regardless of how many dispatch paths exist.

Relevant Files

FileRole
extensions/telegram/src/bot/delivery.replies.ts漏录位置 — Telegram sending layer
extensions/telegram/src/bot-message-dispatch.tsCalls deliverReplies
src/infra/outbound/deliver.tsReference implementation (hooks fire correctly)
src/infra/outbound/outbound-send-service.tsSeparate gap: plugin hook not triggered
workspace/hooks/conversation-stream/handler.tsAffected hook

Tags

bug, hooks, telegram, architecture

PR fix notes

PR #50934: fix(telegram): emit message:sent hook for streaming replies

Description (problem / solution / changelog)

Summary

Fix Telegram streaming replies not triggering the message:sent hook, causing outbound messages to be missing from conversation-stream JSONL.

Root Cause

Streaming replies in Telegram go through draft-stream.ts which directly sends messages via the Telegram API. Unlike deliverReplies (used for non-streaming replies), streaming does NOT call emitMessageSentHooks, so the message:sent internal hook never fires.

Fix

In bot-message-dispatch.ts:

  1. Added onMessageSent callback parameter to createDraftLane
  2. Created streamOnMessageSent callback that calls emitMessageSentHooks when a streaming message is successfully sent
  3. Passed streamOnMessageSent to createTelegramDraftStream via the onMessageSent option

This mirrors what deliverReplies already does internally — just intercepted at the streaming callback layer.

Testing

  • Verified: msgId=5016 (streaming outbound reply) now appears in memory/conversations/telegram__695909778/2026-03-20.jsonl
  • Telegram bot replies work correctly for both streaming and non-streaming paths

Related

See also a2a6f2cf1 (fix: bridge direct delivery to internal message:sent hooks) which fixed the non-streaming path.

Fixes: openclaw/openclaw#50126

Changed files

  • extensions/telegram/src/bot-message-dispatch.test.ts (modified, +36/-0)
  • extensions/telegram/src/bot-message-dispatch.ts (modified, +30/-3)
  • extensions/telegram/src/bot/delivery.replies.ts (modified, +14/-1)
  • extensions/telegram/src/draft-stream.ts (modified, +9/-0)

Code Example

// extensions/telegram/src/bot-message-dispatch.ts
await deliverReplies({
// ...existing params
sessionKeyForInternalHooks: ctxPayload.SessionKey,
hookRunner: /* obtain via runtime or global */,
});
RAW_BUFFERClick to expand / collapse

Problem Description

OpenClaw currently has multiple independent outbound message dispatch paths, but hook triggering coverage is inconsistent. Some outbound messages never trigger message:sent (internal) or message_sent (plugin) hooks, causing downstream hooks (e.g. conversation-stream) to miss sent messages.

Confirmed missing scenario: Telegram DM agent text replies are not written to the conversation-stream workspace hook JSONL log.

Root Cause Analysis

Current Outbound Paths

PathSourcemessage:sent (internal)message_sent (plugin)
deliverOutboundPayloads (Core)src/infra/outbound/deliver.ts✅ requires sessionKey
deliverReplies (Telegram)extensions/telegram/src/bot/delivery.replies.ts❌ sessionKey not passed❌ hookRunner not passed
outbound-send-service (plugin tool)src/infra/outbound/outbound-send-service.ts✅ conditionalGap
respond callback (RPC)src/gateway/server-methods/agent.ts❌ not triggered

Key Issues

  1. deliverReplies gap: When deliverReplies is called from bot-message-dispatch.ts, sessionKeyForInternalHooks and hookRunner are not passed. Inside emitMessageSentHooks(), all conditions fail and no hooks ever fire.
  2. Architectural fragmentation: deliverReplies and deliverOutboundPayloads have overlapping functionality but do not share an abstraction. Each channel implements sending independently.
  3. plugin message_sent gap: The outbound-send-service plugin-handled path cannot trigger the plugin hook (documented gap, separate from this issue).

Reproduction Steps

  1. Enable the conversation-stream workspace hook (listens to message:sent event)
  2. Send a message to the bot via Telegram DM
  3. Bot replies with direct text (not via the message tool)
  4. Observe: the outbound reply is missing from memory/conversations/telegram__<peer>/<date>.jsonl

Impact

  • conversation-stream hook: outbound messages are missing from session history
  • Any plugin relying on message:sent / message_sent hooks: may miss delivery events

Suggested Fixes

Option A — Minimal change (recommended for quick fix)

Pass sessionKeyForInternalHooks and hookRunner when calling deliverReplies in bot-message-dispatch.ts:

// extensions/telegram/src/bot-message-dispatch.ts
await deliverReplies({
// ...existing params
sessionKeyForInternalHooks: ctxPayload.SessionKey,
hookRunner: /* obtain via runtime or global */,
});

Option B — Architectural

Introduce a unified OutboundDeliveryObserver that every channel dispatch path calls exactly once. All hooks are mounted at this layer — no longer dependent on each path manually firing hooks. This eliminates coverage gaps regardless of how many dispatch paths exist.

Relevant Files

FileRole
extensions/telegram/src/bot/delivery.replies.ts漏录位置 — Telegram sending layer
extensions/telegram/src/bot-message-dispatch.tsCalls deliverReplies
src/infra/outbound/deliver.tsReference implementation (hooks fire correctly)
src/infra/outbound/outbound-send-service.tsSeparate gap: plugin hook not triggered
workspace/hooks/conversation-stream/handler.tsAffected hook

Tags

bug, hooks, telegram, architecture

extent analysis

Fix Plan

To address the issue of inconsistent hook triggering coverage for outbound messages, we will implement Option A — Minimal change as a quick fix.

Step-by-Step Solution:

  1. Modify bot-message-dispatch.ts: Pass sessionKeyForInternalHooks and hookRunner when calling deliverReplies.
// extensions/telegram/src/bot-message-dispatch.ts
await deliverReplies({
  // ...existing params
  sessionKeyForInternalHooks: ctxPayload.SessionKey,
  hookRunner: /* obtain via runtime or global */,
});
  1. Obtain hookRunner: Ensure hookRunner is properly obtained via runtime or global context to trigger hooks correctly.
  2. Verify deliverReplies: Confirm that deliverReplies in bot/delivery.replies.ts correctly handles the passed sessionKeyForInternalHooks and hookRunner to trigger internal and plugin hooks.

Verification

To verify the fix:

  1. Reproduce the issue: Follow the reproduction steps to ensure the issue is resolved.
  2. Check conversation-stream logs: Verify that the outbound reply is now correctly logged in memory/conversations/telegram__<peer>/<date>.jsonl.
  3. Test plugin hooks: Confirm that any plugin relying on message:sent or message_sent hooks now correctly receives delivery events.

Extra Tips

  • Consider implementing Option B — Architectural for a long-term solution to unify outbound message dispatch paths and eliminate coverage gaps.
  • Review other channel implementations to ensure consistent hook triggering coverage.
  • Document the fix and update relevant files to reflect changes and improve maintainability.

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