openclaw - ✅(Solved) Fix Slack message hooks lose bot alert text when command body is blank [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#76035Fetched 2026-05-03 04:43:07
View on GitHub
Comments
1
Participants
2
Timeline
4
Reactions
2
Author
Timeline (top)
closed ×1commented ×1cross-referenced ×1unsubscribed ×1

Fix Action

Fixed

PR fix notes

PR #76036: Fix Slack hook content fallback for bot alerts

Description (problem / solution / changelog)

Summary

  • Problem: Slack bot alert messages can have empty top-level text and empty command text while their user-visible alert body lives in attachment text.
  • Why it matters: OpenClaw could still reply because Slack prepare preserved the alert body in RawBody and BodyForAgent, but plugin message_received.content stayed empty, so plugin listeners could not match or process the current message.
  • Discovery context: I found this while running a local/private plugin listener against a Slack alert channel. The Slack event reached the plugin hook, but message_received.content was blank even though OpenClaw itself could answer the same alert.
  • What changed: Hook content now uses BodyForCommands only when it is nonblank, then falls back to RawBody, then Body. Slack prepare regression coverage also asserts alert attachment text stays available to the agent while command fields remain blank.
  • What did NOT change (scope boundary): Slack command detection and agent prompt enrichment still use their existing fields. Nonblank command text, such as /status, still wins over raw message text for hook content.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #76035
  • Related: N/A
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: deriveInboundMessageHookContext() treated any string BodyForCommands, including "" or whitespace-only text, as the canonical hook content and never reached the RawBody fallback.
  • Missing detection / guardrail: Hook mapper tests did not cover blank command content with nonblank raw inbound content, and the Slack prepare regression did not assert that bot alert attachment text leaves command fields blank while preserving RawBody/BodyForAgent.
  • Contributing context (if known): Slack bot alert events can use subtype: "bot_message" with empty top-level text while putting the visible alert text in attachment text.

Slack Schema Evidence

  • Slack's message event reference defines channel messages and notes that messages can include an attachments property.
  • Slack's bot_message subtype reference defines subtype: "bot_message" as the event shape for integration bot messages.
  • Slack Node SDK's BotMessageEvent type includes type: "message", subtype: "bot_message", top-level text, and optional attachments?: MessageAttachment[].
  • Slack Node SDK's MessageAttachment type defines attachment text as the attachment's main body text, so an alert body in attachments[0].text is a valid Slack payload shape even when command text is blank.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file:
    • src/hooks/message-hook-mappers.test.ts
    • extensions/slack/src/monitor/message-handler/prepare.test.ts
  • Scenario the test should lock in: A blank or whitespace-only command body falls back to raw inbound hook content, while a nonblank command body remains preferred. Slack bot alert attachment text remains in RawBody and BodyForAgent while CommandBody and BodyForCommands stay blank.
  • Why this is the smallest reliable guardrail: The bug is in the shared hook mapper selection rule, with a Slack prepare fixture proving the channel-specific input shape that triggered it.
  • Existing test that already covers this (if any): None before this PR.
  • If no new test is added, why not: N/A.

User-visible / Behavior Changes

Plugin and internal message:received hooks can now receive raw inbound content when command text is blank. For Slack bot alerts, this means plugin listeners see the alert body instead of content: "".

Diagram (if applicable)

Before:
Slack bot alert attachment text
  -> Slack prepare RawBody/BodyForAgent populated, BodyForCommands blank
  -> hook mapper selects blank BodyForCommands
  -> plugin message_received.content = ""

After:
Slack bot alert attachment text
  -> Slack prepare RawBody/BodyForAgent populated, BodyForCommands blank
  -> hook mapper skips blank command text and falls back to RawBody
  -> plugin message_received.content = alert body

Security Impact (required)

  • New permissions/capabilities? (Yes/No) No
  • Secrets/tokens handling changed? (Yes/No) No
  • New/changed network calls? (Yes/No) No
  • Command/tool execution surface changed? (Yes/No) No
  • Data access scope changed? (Yes/No) No
  • If any Yes, explain risk + mitigation: N/A

Repro + Verification

Environment

  • OS: macOS local checkout
  • Runtime/container: Node 22 via repo test wrapper
  • Model/provider: N/A
  • Integration/channel (if any): Slack
  • Relevant config (redacted): Slack account with bot-authored messages allowed

Steps

  1. Receive a Slack message event shaped like a bot alert: subtype: "bot_message", top-level text: "", and attachment text containing the visible alert body.
  2. Slack prepare extracts the attachment text into the current message body for agent processing, while command text stays blank.
  3. Emit plugin/internal message_received hook context from the finalized inbound message.

Expected

  • RawBody and BodyForAgent contain the Slack alert text.
  • CommandBody and BodyForCommands are blank.
  • message_received.content contains the Slack alert text.
  • Nonblank command content still wins over raw inbound content.

Actual

  • Before this PR, message_received.content was "" because the mapper selected blank BodyForCommands and did not fall back to RawBody.
  • After this PR, blank command content is skipped and RawBody becomes hook content.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Targeted proof:

  • pnpm test src/hooks/message-hook-mappers.test.ts extensions/slack/src/monitor/message-handler/prepare.test.ts -- --reporter=verbose
  • pnpm test:changed
  • pnpm exec oxfmt --check --threads=1 src/hooks/message-hook-mappers.ts src/hooks/message-hook-mappers.test.ts extensions/slack/src/monitor/message-handler/prepare.test.ts docs/automation/hooks.md
  • pnpm check:docs
  • git diff --check
  • pnpm changed:lanes --json
  • pnpm check:changed

Human Verification (required)

  • Author-side verification performed before opening this PR:
    • Hook mapper falls back from blank/whitespace command content to raw inbound content.
    • Hook mapper keeps nonblank command content ahead of raw inbound content.
    • Slack bot alert fixture preserves attachment text in RawBody and BodyForAgent while command fields are blank.
  • Edge cases covered by the targeted tests:
    • Whitespace-only BodyForCommands.
    • Nonblank command text such as /status.
  • Out of scope / not claimed by this PR:
    • Live Slack delivery.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes/No) Yes
  • Config/env changes? (Yes/No) No
  • Migration needed? (Yes/No) No
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: The hook content fallback rule is shared across channels, so other blank-command inbound contexts can now expose raw inbound content to message_received hooks where they previously emitted blank content.
    • Why acceptable: The new mapper tests lock in the intended generic precedence rule, and docs now state that hook content prefers nonblank command text before raw inbound body. This PR treats the fallback as the shared hook contract because message_received.content is the representative current-message body, not a command-parser-only field.

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • docs/automation/hooks.md (modified, +1/-1)
  • extensions/slack/src/monitor/message-handler/prepare.test.ts (modified, +6/-1)
  • src/agents/transcript-policy.ts (modified, +8/-7)
  • src/hooks/message-hook-mappers.test.ts (modified, +24/-0)
  • src/hooks/message-hook-mappers.ts (modified, +8/-7)
RAW_BUFFERClick to expand / collapse

Problem

Slack bot messages can have empty top-level text while carrying the user-visible message in attachment text. OpenClaw's Slack prepare path extracts that text into RawBody and BodyForAgent, so the agent can reply. The plugin hook mapper previously treated an empty string BodyForCommands as the canonical hook content and did not fall back to RawBody, so plugin message_received listeners saw content: "".

Discovery Context

I found this while running a local/private plugin listener against a Slack alert channel. The Slack event reached the plugin hook, but the hook payload had message_received.content: "", so the plugin correctly treated it as an empty message and ignored it.

The confusing part was that OpenClaw itself could reply to the same Slack alert. That showed Slack extraction had not lost the alert text entirely: the agent path still had usable RawBody/BodyForAgent, while the plugin hook mapper had selected blank BodyForCommands as the representative content and stopped before the RawBody fallback.

Slack Schema Evidence

  • Slack's message event reference defines channel messages and notes that messages can include an attachments property.
  • Slack's bot_message subtype reference defines subtype: "bot_message" as the event shape for integration bot messages.
  • Slack Node SDK's BotMessageEvent type includes type: "message", subtype: "bot_message", top-level text, and optional attachments?: MessageAttachment[].
  • Slack Node SDK's MessageAttachment type defines attachment text as the attachment's main body text, so an alert body in attachments[0].text is a valid Slack payload shape even when command text is blank.

Reproduction Shape

  1. Receive a Slack message event with subtype: "bot_message", text: "", and attachments[0].text containing alert text such as Readiness probe failed.
  2. Allow bot-authored Slack messages for that account.
  3. Observe Slack prepare output has RawBody and BodyForAgent containing the alert text, while CommandBody and BodyForCommands are blank.
  4. Observe a plugin message_received hook receives blank content.

Expected Behavior

Plugin message_received.content should use a nonblank command body when one exists, but fall back to RawBody and then Body when command text is blank.

Acceptance Criteria

  • Slack bot alert attachment text remains extracted into RawBody and BodyForAgent.
  • Blank or whitespace-only BodyForCommands no longer blocks hook content fallback.
  • Nonblank command text, such as /status, still wins over raw message text.
  • Plugin and internal message:received contexts expose the same canonical content.
  • Targeted Slack prepare and hook mapper tests cover the regression.

extent analysis

TL;DR

The plugin hook mapper should be updated to fall back to RawBody when BodyForCommands is empty, ensuring that plugin message_received listeners receive the correct content.

Guidance

  • Review the plugin hook mapper code to identify where it selects BodyForCommands as the representative content and update it to check for empty strings.
  • Add a fallback to use RawBody when BodyForCommands is empty, while still prioritizing nonblank command text.
  • Verify that the updated code handles cases where BodyForCommands is blank or contains only whitespace.
  • Test the changes with the reproduction shape provided to ensure that the plugin message_received hook receives the correct content.

Example

// Pseudocode example, actual implementation may vary
if (BodyForCommands && BodyForCommands.trim() !== '') {
  content = BodyForCommands;
} else {
  content = RawBody;
}

Notes

The exact implementation details may depend on the specific programming language and framework used in the plugin hook mapper. The provided example is a simplified illustration of the fallback logic.

Recommendation

Apply the workaround by updating the plugin hook mapper to fall back to RawBody when BodyForCommands is empty, as this will ensure that plugin message_received listeners receive the correct content.

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

openclaw - ✅(Solved) Fix Slack message hooks lose bot alert text when command body is blank [1 pull requests, 1 comments, 2 participants]