openclaw - ✅(Solved) Fix Expose messageId in plugin SDK message_sent hook (toPluginMessageSentEvent) [2 pull requests, 1 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#66729Fetched 2026-04-15 06:24:39
View on GitHub
Comments
0
Participants
1
Timeline
3
Reactions
0
Participants
Timeline (top)
cross-referenced ×2referenced ×1

Error Message

// Current (2026.4.14) function toPluginMessageSentEvent(canonical) { return { to: canonical.to, content: canonical.content, success: canonical.success, ...canonical.error ? { error: canonical.error } : {} }; }

Fix Action

Fix / Workaround

Current Workaround

We patch the compiled JS in all 6 chunks that contain toPluginMessageSentEvent. This has to be reapplied on every OpenClaw update.

PR fix notes

PR #66742: Plugins: expose native messageId on message_sent hook event

Description (problem / solution / changelog)

Summary

  • Problem: toPluginMessageSentEvent strips messageId from the canonical sent context before dispatching the message_sent plugin hook, even though the channel already returned a native message id (already present on canonical.messageId and on the internal toInternalMessageSentContext). Plugins that want to react to, audit, or correlate with the just-sent message have to patch compiled JS chunks on every upgrade.
  • Why it matters: Unblocks canary/health-check plugins, reaction plugins, and audit/logging plugins that need the native platform id of a message the bot just sent. Matches the internal hook context, which already carries messageId.
  • What changed: Added optional messageId?: string to PluginHookMessageSentEvent; toPluginMessageSentEvent now forwards canonical.messageId when the channel provided one (conditional spread so the shape stays unchanged when the channel did not return an id). Covered by mapper tests.
  • What did NOT change (scope boundary): No changes to hook dispatch/ordering, no changes to channel adapters, no change to the internal toInternalMessageSentContext (already correct), and the plugin-sdk API baseline (pnpm plugin-sdk:api:check) still passes.

Change Type (select all)

  • Bug fix
  • Feature

Scope (select all touched areas)

  • API / contracts
  • Integrations

Linked Issue/PR

  • Closes #66729
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: toPluginMessageSentEvent was introduced as a minimal plugin-facing projection of CanonicalSentMessageHookContext. It omitted messageId while the internal variant (toInternalMessageSentContext) retained it, so plugin SDK consumers lost the native id at the hook boundary.
  • Missing detection / guardrail: The mapper test only asserted fields it cared about at the time — messageId was never explicitly exercised for the plugin path, so the asymmetry with the internal path went unnoticed.
  • Contributing context: canonical.messageId has existed on CanonicalSentMessageHookContext the whole time — this is purely a projection bug, not a plumbing bug.

Regression Test Plan

  • Coverage level:
    • Unit test
  • Target test or file: src/hooks/message-hook-mappers.test.ts
  • Scenario the test locks in:
    • toPluginMessageSentEvent includes messageId when the canonical context has one.
    • toPluginMessageSentEvent omits messageId entirely when the canonical context does not have one (so the event shape stays minimal for adapters that do not return an id).
  • Why this is the smallest reliable guardrail: The bug is a pure projection issue; a unit test on the mapper catches both the regression and any future drift between the plugin and internal sent events.

User-visible / Behavior Changes

Plugin authors listening on message_sent will now receive the native channel messageId on the event object when the underlying adapter returned one. The field is optional, so existing plugins that do not read it are unaffected.

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No — the id was already present on the internal hook context and on canonical; this change only re-exposes it on the plugin-facing projection. No new data crosses a trust boundary.

Repro + Verification

Environment

  • OS: macOS 26.4.1 (arm64)
  • Runtime/container: Node 22, pnpm 10.32.1 (per repo packageManager)

Steps

  1. Register a plugin with a message_sent hook and log the incoming event.
  2. Have the bot send a message through any channel that returns a native id (WhatsApp / Telegram / Discord).
  3. Inspect the event object in the plugin.

Expected

  • event.messageId is the native channel id of the message that was just sent.

Actual (before this PR)

  • event.messageId is undefined; plugins have to patch compiled chunks of message-hook-mappers to recover it.

Evidence

  • pnpm vitest run src/hooks/message-hook-mappers.test.ts → 10/10 passing (existing 8 + 2 new).
  • pnpm vitest run src/plugins/wired-hooks-message.test.ts → 4/4 passing (no regression on dispatch-side type usage).
  • pnpm plugin-sdk:api:check → baseline unchanged.

Human Verification (required)

  • Verified scenarios: Focused unit runs listed above; manually inspected the diff vs toInternalMessageSentContext to confirm field-level parity for messageId.
  • Edge cases checked:
    • messageId absent on the canonical context → plugin event stays minimal (no messageId key at all — test covers this).
    • messageId present → plugin event carries it.
    • Error path (success: false) still carries error alongside messageId.
  • What I did not verify: End-to-end wire through a live channel adapter (no live channel creds in this environment). The canonical contexts already carry messageId per the existing mappers and tests, so the remaining wiring is already exercised upstream of the hook boundary.

Compatibility / Migration

  • Backward compatible? Yes — the new field is optional and the type only gains a property.
  • Config/env changes? No.
  • Migration needed? No.

Risks and Mitigations

  • Risk: Plugins that were working around the bug by reading messageId from elsewhere continue to work but now have the field natively; no action required. Mitigation: field is additive and optional.

Changed files

  • CHANGELOG.md (modified, +3/-0)
  • src/hooks/message-hook-mappers.test.ts (modified, +33/-0)
  • src/hooks/message-hook-mappers.ts (modified, +1/-0)
  • src/plugins/hook-message.types.ts (modified, +1/-0)

PR #66770: feat(plugin-sdk): expose messageId on plugin message_sent hook event

Description (problem / solution / changelog)

Summary

  • Fixes #66729
  • Plugins hooking `message_sent` need the native channel message ID (WhatsApp/Telegram/Discord, etc.) that the adapter returns after a send — for reaction plugins, edit/delete tracking, audit trails, and canary health-checks.
  • `CanonicalSentMessageHookContext` already carries `messageId`, and `toInternalMessageSentContext` forwards it to the internal hook event, but `toPluginMessageSentEvent` was filtering it out for plugin-facing consumers. Users in the issue report patching the compiled JS on every release to reinstate it.

Change

  • Add `messageId?: string` to `PluginHookMessageSentEvent`.
  • In `toPluginMessageSentEvent`, spread `messageId` only when the adapter provided one, keeping the event shape minimal for adapters or failed sends that don't have one.

Test plan

  • `pnpm vitest run src/hooks/message-hook-mappers.test.ts` — 10/10 pass
  • New regression tests cover: (a) messageId present on the event when provided, (b) absent when not provided
  • `pnpm check` (via pre-commit) — green

AI-assisted disclosure

  • AI-assisted (Claude)
  • Testing: fully tested — two new regression cases plus existing coverage.
  • Prompt context: issue #66729 before/after snippets, plus reads of `src/hooks/message-hook-mappers.ts` / `.test.ts` and `src/plugins/hook-message.types.ts`.
  • I understand what the code does.

Changed files

  • src/hooks/message-hook-mappers.test.ts (modified, +55/-0)
  • src/hooks/message-hook-mappers.ts (modified, +15/-0)
  • src/plugins/hook-message.types.ts (modified, +6/-0)

Code Example

// Current (2026.4.14)
function toPluginMessageSentEvent(canonical) {
    return {
        to: canonical.to,
        content: canonical.content,
        success: canonical.success,
        ...canonical.error ? { error: canonical.error } : {}
    };
}

---

function toPluginMessageSentEvent(canonical) {
    return {
        to: canonical.to,
        content: canonical.content,
        success: canonical.success,
        messageId: canonical.messageId,
        ...canonical.error ? { error: canonical.error } : {}
    };
}
RAW_BUFFERClick to expand / collapse

Problem

The message_sent plugin hook (toPluginMessageSentEvent) does not include the native messageId returned by the channel (WhatsApp/Telegram/Discord) after sending a message.

The messageId already exists in the canonical object (it comes from the channel's send response), but toPluginMessageSentEvent filters it out:

// Current (2026.4.14)
function toPluginMessageSentEvent(canonical) {
    return {
        to: canonical.to,
        content: canonical.content,
        success: canonical.success,
        ...canonical.error ? { error: canonical.error } : {}
    };
}

Note: toInternalMessageSentContext already includes messageId — only the plugin-facing version is missing it.

Proposed Fix

Add messageId to the return object:

function toPluginMessageSentEvent(canonical) {
    return {
        to: canonical.to,
        content: canonical.content,
        success: canonical.success,
        messageId: canonical.messageId,
        ...canonical.error ? { error: canonical.error } : {}
    };
}

Use Case

Plugins that need to track sent messages by their native platform ID. Examples:

  • Canary/health-check plugins that send a test message and need to match the response by messageId
  • Reaction plugins that need to react to a message the bot just sent
  • Audit/logging plugins that record the native message ID for traceability

Current Workaround

We patch the compiled JS in all 6 chunks that contain toPluginMessageSentEvent. This has to be reapplied on every OpenClaw update.

Environment

  • OpenClaw 2026.4.14
  • Affected files (compiled): message-hook-mappers-*.js and related chunks

extent analysis

TL;DR

Update the toPluginMessageSentEvent function to include the messageId property from the canonical object in the returned event.

Guidance

  • Review the proposed fix and apply it to the toPluginMessageSentEvent function to include the messageId in the plugin-facing event.
  • Verify that the messageId is correctly included in the event by checking the output of the toPluginMessageSentEvent function.
  • Consider the use cases mentioned, such as canary/health-check plugins, reaction plugins, and audit/logging plugins, to ensure the updated event meets their requirements.
  • Be aware that the current workaround of patching the compiled JS files will need to be reapplied on every OpenClaw update, making the proposed fix a more permanent solution.

Example

function toPluginMessageSentEvent(canonical) {
    return {
        to: canonical.to,
        content: canonical.content,
        success: canonical.success,
        messageId: canonical.messageId,
        ...canonical.error ? { error: canonical.error } : {}
    };
}

Notes

The proposed fix assumes that the messageId property exists in the canonical object. If this property is not always present, additional error handling may be necessary.

Recommendation

Apply the proposed fix to update the toPluginMessageSentEvent function, as it provides a more permanent solution and meets the requirements of the mentioned use cases.

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