openclaw - ✅(Solved) Fix Align message_sent hooks with documented Slack delivery correlation [1 pull requests, 1 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#78046Fetched 2026-05-06 06:17:34
View on GitHub
Comments
1
Participants
2
Timeline
4
Reactions
2
Author
Assignees
Timeline (top)
assigned ×1commented ×1cross-referenced ×1labeled ×1

The documented message hook contract says message_sent observes final success/failure and message hook contexts expose stable correlation fields when available (ctx.sessionKey, ctx.runId, ctx.messageId, etc.). In the current implementation, the normal Slack inbound assistant reply path does not appear to emit message_sent, and the generic outbound path emits message_sent without passing available session/run correlation into the canonical hook context.

This makes plugins unable to reliably observe the Slack-delivered assistant message timestamp (response.ts) for normal Slack agent replies. One concrete downstream impact: plugins that need to react to the final assistant Slack message cannot use message hooks as documented and may accidentally fall back to transcript-local message IDs, which are UUIDs and not valid Slack timestamps.

Error Message

  • failure emits message_sent with success: false and a useful error without leaking secrets;

Root Cause

Plugins need a stable way to associate delivered channel messages with the agent session/run that produced them. For Slack, the delivered timestamp is required for follow-up actions such as adding reactions, editing, deleting, or threading against a specific assistant message. Without message_sent on normal Slack replies, or without session/run correlation on emitted hooks, plugins have to rely on brittle transcript/session-store inference.

Fix Action

Fix / Workaround

  • extensions/slack/src/monitor/message-handler/dispatch.ts calls Slack monitor reply delivery (deliverNormally / deliverReplies).
  • extensions/slack/src/monitor/replies.ts calls sendMessageSlack(...).
  • extensions/slack/src/send.ts posts via Slack chat.postMessage.
  • I did not find a message_sent emission on that normal Slack reply path.

PR fix notes

PR #78103: Fix plugin webhooks for Slack reply delivery

Description (problem / solution / changelog)

Summary

This fixes #78046 by making the message sent hook contract line up with the paths that actually deliver replies.

Slack inbound assistant replies do not always travel through the generic outbound delivery code. The normal reply path, draft preview finalization path, and native Slack streaming path can all produce visible Slack messages without the generic message_sent emitter seeing the delivery. This change emits message_sent and internal message:sent from those Slack-owned delivery points, using the delivered Slack timestamp when Slack returns one.

The generic outbound path already computed a session key for internal hooks, but it was not forwarded into the canonical sent-hook context. This PR also forwards that session key so plugin hooks and internal hooks get consistent session correlation.

What changed

  • Added Slack message_sent / internal message:sent emission for deliverReplies success and failure.
  • Preserved Slack provider message IDs from chat.postMessage responses in sent-hook payloads.
  • Used Slack block fallback text for block-only reply hook content, matching the text Slack sends with the block payload.
  • Emitted sent hooks when finalized Slack draft previews are edited in place instead of sent through deliverReplies.
  • Emitted sent hooks for native Slack streaming replies, including partial fallback cases without double-counting buffered fallback text.
  • Propagated generic outbound sessionKeyForInternalHooks into the canonical sent-hook context.

Regression coverage

  • Slack delivered message IDs and session context on successful reply delivery.
  • Slack failure payload shape before rethrowing send errors.
  • Internal message:sent emission when only session hook context is available.
  • Block-only Slack replies using fallback text for hook content.
  • Draft preview finalization emitting sent hooks without duplicate normal delivery.
  • Native Slack streaming sent hooks, including stop-time fallback and append-fallback accounting.
  • Generic outbound message_sent context including sessionKey.

Validation

  • pnpm exec vitest run extensions/slack/src/monitor/replies.test.ts extensions/slack/src/monitor/message-handler/dispatch.preview-fallback.test.ts src/infra/outbound/deliver.test.ts
  • pnpm check:changed
  • codex review --base origin/main

AI-assisted change prepared with Codex.

Changed files

  • extensions/slack/src/monitor/message-handler/dispatch.preview-fallback.test.ts (modified, +121/-0)
  • extensions/slack/src/monitor/message-handler/dispatch.ts (modified, +56/-1)
  • extensions/slack/src/monitor/replies.test.ts (modified, +150/-0)
  • extensions/slack/src/monitor/replies.ts (modified, +118/-10)
  • src/infra/outbound/deliver.test.ts (modified, +11/-2)
  • src/infra/outbound/deliver.ts (modified, +1/-0)
RAW_BUFFERClick to expand / collapse

Summary

The documented message hook contract says message_sent observes final success/failure and message hook contexts expose stable correlation fields when available (ctx.sessionKey, ctx.runId, ctx.messageId, etc.). In the current implementation, the normal Slack inbound assistant reply path does not appear to emit message_sent, and the generic outbound path emits message_sent without passing available session/run correlation into the canonical hook context.

This makes plugins unable to reliably observe the Slack-delivered assistant message timestamp (response.ts) for normal Slack agent replies. One concrete downstream impact: plugins that need to react to the final assistant Slack message cannot use message hooks as documented and may accidentally fall back to transcript-local message IDs, which are UUIDs and not valid Slack timestamps.

Evidence

Normal Slack inbound reply path appears to bypass generic outbound hook emission:

  • extensions/slack/src/monitor/message-handler/dispatch.ts calls Slack monitor reply delivery (deliverNormally / deliverReplies).
  • extensions/slack/src/monitor/replies.ts calls sendMessageSlack(...).
  • extensions/slack/src/send.ts posts via Slack chat.postMessage.
  • I did not find a message_sent emission on that normal Slack reply path.

Generic outbound does emit message_sent, but drops available correlation:

  • src/infra/outbound/deliver.ts computes sessionKeyForInternalHooks = params.mirror?.sessionKey ?? params.session?.key.
  • createMessageSentEmitter(...) builds canonical sent context with to, content, success, channelId, accountId, conversationId, and messageId, but does not pass sessionKey or runId.
  • src/hooks/message-hook-mappers.ts supports sessionKey and runId in buildCanonicalSentMessageHookContext, and toPluginMessageSentEvent / toPluginMessageContext will expose them if provided.

Slack delivered message ID is available:

  • extensions/slack/src/send.ts returns Slack response.ts as messageId for posted Slack messages.
  • Generic deliverOutboundPayloads passes delivery messageId into emitMessageSent.

Transcript message IDs are not a safe substitute:

  • src/config/sessions/transcript-append.ts generates a random UUID for transcript entries.
  • That UUID is what session transcript update consumers see as the transcript message ID; it is not a channel-delivered Slack timestamp.

Expected behavior

  • Normal Slack assistant reply delivery should emit message_sent after final delivery success/failure.
  • For successful Slack sends, ctx.messageId / event.messageId should be the Slack message timestamp returned by Slack (response.ts).
  • When OpenClaw has correlation fields available, message_sent should include them in first-class fields:
    • ctx.sessionKey / event.sessionKey
    • ctx.runId / event.runId
    • ideally typed threadId / replyToId on message sent/sending surfaces where relevant, matching the docs guidance to prefer typed fields before channel-specific metadata.
  • Generic outbound delivery should pass available params.session?.key and any available run id into buildCanonicalSentMessageHookContext.

Why this matters

Plugins need a stable way to associate delivered channel messages with the agent session/run that produced them. For Slack, the delivered timestamp is required for follow-up actions such as adding reactions, editing, deleting, or threading against a specific assistant message. Without message_sent on normal Slack replies, or without session/run correlation on emitted hooks, plugins have to rely on brittle transcript/session-store inference.

Suggested fix

  1. Emit message_sent from the normal Slack inbound assistant reply path, including the Slack delivered messageId.
  2. Pass available sessionKey and runId through generic outbound createMessageSentEmitter into buildCanonicalSentMessageHookContext.
  3. Add regression tests for:
    • normal Slack agent reply emits message_sent with delivered Slack messageId;
    • generic deliverOutboundPayloads includes sessionKey when params.session.key is present;
    • failure emits message_sent with success: false and a useful error without leaking secrets;
    • hook payload shape aligns with the documented first-class correlation fields.

extent analysis

TL;DR

Emit message_sent from the normal Slack inbound assistant reply path and pass available correlation fields to the canonical hook context to fix the issue.

Guidance

  • Identify the normal Slack inbound assistant reply path in extensions/slack/src/monitor/message-handler/dispatch.ts and add an emission of message_sent with the delivered Slack messageId.
  • Modify createMessageSentEmitter in src/infra/outbound/deliver.ts to include sessionKey and runId in the canonical sent context when available.
  • Review the buildCanonicalSentMessageHookContext function in src/hooks/message-hook-mappers.ts to ensure it correctly handles the added correlation fields.
  • Add regression tests to verify the fixes, including tests for normal Slack agent reply, generic outbound delivery, failure cases, and hook payload shape.

Example

// In extensions/slack/src/monitor/message-handler/dispatch.ts
deliverNormally(reply) {
  // ...
  const messageId = sendMessageSlack(reply);
  emit('message_sent', { messageId, sessionKey: reply.sessionKey, runId: reply.runId });
}

Notes

The suggested fix assumes that the sessionKey and runId are available in the normal Slack inbound assistant reply path. If these fields are not available, additional modifications may be necessary to retrieve or generate them.

Recommendation

Apply the suggested fix to emit message_sent from the normal Slack inbound assistant reply path and pass available correlation fields to the canonical hook context. This will provide a stable way for plugins to associate delivered channel messages with the agent session/run that produced them.

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…

FAQ

Expected behavior

  • Normal Slack assistant reply delivery should emit message_sent after final delivery success/failure.
  • For successful Slack sends, ctx.messageId / event.messageId should be the Slack message timestamp returned by Slack (response.ts).
  • When OpenClaw has correlation fields available, message_sent should include them in first-class fields:
    • ctx.sessionKey / event.sessionKey
    • ctx.runId / event.runId
    • ideally typed threadId / replyToId on message sent/sending surfaces where relevant, matching the docs guidance to prefer typed fields before channel-specific metadata.
  • Generic outbound delivery should pass available params.session?.key and any available run id into buildCanonicalSentMessageHookContext.

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 - ✅(Solved) Fix Align message_sent hooks with documented Slack delivery correlation [1 pull requests, 1 comments, 2 participants]