openclaw - ✅(Solved) Fix Slack message_tool_only source replies fail because durable send requires reconcileUnknownSend [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#84078Fetched 2026-05-20 03:44:19
View on GitHub
Comments
2
Participants
2
Timeline
10
Reactions
1
Timeline (top)
labeled ×7commented ×2cross-referenced ×1

Slack source replies can be forced through the message tool via sourceReplyDeliveryMode: "message_tool_only" (for example for Slack group/channel visible replies when configured that way). In that path, message(action="send") currently performs a required durable-send preflight that unconditionally requires reconcileUnknownSend.

The Slack adapter does not appear to expose reconcileUnknownSend, so the send is rejected before any fallback/best-effort delivery can happen:

Required durable message send is unsupported for slack: missing reconcileUnknownSend

This turns a safe source-reply delivery policy into a visible reply failure for Slack.

Error Message

In a Slack channel/thread, a visible assistant reply that must use the message tool fails with:

Root Cause

Slack source replies can be forced through the message tool via sourceReplyDeliveryMode: "message_tool_only" (for example for Slack group/channel visible replies when configured that way). In that path, message(action="send") currently performs a required durable-send preflight that unconditionally requires reconcileUnknownSend.

The Slack adapter does not appear to expose reconcileUnknownSend, so the send is rejected before any fallback/best-effort delivery can happen:

Required durable message send is unsupported for slack: missing reconcileUnknownSend

This turns a safe source-reply delivery policy into a visible reply failure for Slack.

Fix Action

Fixed

PR fix notes

PR #84232: fix(messages): use best-effort for implicit tool-only source replies

Description (problem / solution / changelog)

Summary

  • Force implicit and explicit-current-source message_tool_only replies through best-effort delivery before target normalization.
  • Preserve required durable delivery when the model supplies an explicit external message target with bestEffort: false.
  • Add regression coverage for the Slack-style adapter path that lacks reconcileUnknownSend.

Root Cause

For Slack group/channel source replies with visible replies routed through the message tool, a source-channel reply could still carry bestEffort: false, including the case where the tool explicitly supplied the current source channel as target. That made the outbound runner request required durable delivery, which then failed preflight on Slack because required durable sends need reconcileUnknownSend.

Current-source replies are user-visible conversational replies, not explicit cross-channel durable sends, so they should use best-effort delivery even when the target equals the current source conversation. Explicit external targeted sends keep the existing required durable semantics.

Fixes #84078.

Real behavior proof

  • Behavior or issue addressed: message_tool_only visible source replies that omit an explicit target, or explicitly target the current source Slack channel, no longer fail the durable-send preflight when Slack lacks reconcileUnknownSend; explicit external targeted sends still keep required durable fail-closed behavior.
  • Real environment tested: Local patched OpenClaw checkout at aff69bbcc622, macOS arm64, Node.js v24.15.0, OpenClaw runtime loaded from this branch, real Slack channel adapter, real Slack Web API delivery into a dedicated test channel. Tokens, workspace URL, channel name, and full channel id are redacted.
  • Exact proof route: runMessageAction -> core outbound -> real Slack adapter -> Slack Web API.
  • Exact steps or command run after this patch: node --import tsx --input-type=module <real-slack-source-reply-proof> with the active plugin registry set to the real Slack channel plugin, sourceReplyDeliveryMode: "message_tool_only", toolContext.currentChannelProvider: "slack", and toolContext.currentChannelId set to the same redacted test channel for the two source-reply cases.
  • Evidence after fix:
{
  "status": "PASS",
  "pr": 84232,
  "branch": "fix/slack-message-tool-durable-fallback",
  "head": "aff69bbcc622",
  "route": "runMessageAction -> core outbound -> real Slack adapter -> Slack Web API",
  "channel": {
    "id": "C0B5...B7T",
    "name": "[redacted-test-channel]"
  },
  "marker": "OC84232_1779243865565",
  "sends": [
    {
      "name": "omitted-target-source-reply",
      "kind": "send",
      "channel": "slack",
      "to": "[redacted-test-channel]",
      "handledBy": "core",
      "messageId": "1779243866.436879",
      "via": "direct"
    },
    {
      "name": "explicit-current-source-target",
      "kind": "send",
      "channel": "slack",
      "to": "[redacted-test-channel]",
      "handledBy": "core",
      "messageId": "1779243867.013869",
      "via": "direct"
    }
  ],
  "externalFailure": {
    "name": "external-required-durable",
    "failedClosed": true,
    "error": "Required durable message send is unsupported for slack: missing reconcileUnknownSend"
  },
  "observedCount": 2,
  "observed": [
    {
      "ts": "1779243866.436879",
      "text": "OC84232_1779243865565 omitted target source reply",
      "bot_id": "[present]",
      "user": "[present]"
    },
    {
      "ts": "1779243867.013869",
      "text": "OC84232_1779243865565 explicit current-source target reply",
      "bot_id": "[present]",
      "user": "[present]"
    }
  ]
}
  • Observed result after fix: Both current-source message_tool_only sends completed through the core outbound path and posted to a real Slack test channel even when the input carried bestEffort: false: one omitted the target and one explicitly used the current Slack source channel as target. The explicit external targeted send with bestEffort: false failed before delivery with missing reconcileUnknownSend, preserving the required durable guard for non-source targets.
  • What was not tested: This proof did not run a full live LLM/gateway Slack inbound turn. It specifically validates the patched message-tool send path with the real Slack adapter and real Slack delivery, replacing the earlier local Slack-shaped adapter proof.

Tests

  • node scripts/run-vitest.mjs run --config test/vitest/vitest.infra.config.ts src/infra/outbound/message-action-runner.core-send.test.ts src/infra/outbound/message-action-runner.send-validation.test.ts src/infra/outbound/outbound-send-service.test.ts src/infra/outbound/message.test.ts
  • node scripts/run-vitest.mjs run --config test/vitest/vitest.agents-tools.config.ts src/agents/tools/message-tool.test.ts
  • node scripts/run-tsgo.mjs -p tsconfig.core.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/core.tsbuildinfo

Changed files

  • src/infra/outbound/message-action-runner.core-send.test.ts (modified, +104/-0)
  • src/infra/outbound/message-action-runner.ts (modified, +70/-0)

Code Example

Required durable message send is unsupported for slack: missing reconcileUnknownSend

---

if (chatType === "group" || chatType === "channel") mode =
  (params.cfg.messages?.groupChat?.visibleReplies ?? params.cfg.messages?.visibleReplies) === "message_tool"
    ? "message_tool_only"
    : "automatic";

---

function deriveRequiredMessageSendCapabilities(params) {
  const requirements = { reconcileUnknownSend: true };
  for (const payload of params.payloads) mergeDurableRequirements(requirements, deriveDurableFinalDeliveryRequirements({
    payload,
    replyToId: params.replyToId,
    threadId: params.threadId,
    silent: params.silent,
    payloadTransport: payloadRequiresDurablePayloadTransport(payload),
    batch: params.payloads.length > 1,
    reconcileUnknownSend: true
  }));
  return requirements;
}

---

throw new Error(`Required durable message send is unsupported for ${params.channel}: ${suffix}`);

---

Best-effort final delivery does not require `reconcileUnknownSend` ...
Required durable final delivery must explicitly require `reconcileUnknownSend`.

---

Required durable message send is unsupported for slack: missing reconcileUnknownSend
RAW_BUFFERClick to expand / collapse

Summary

Slack source replies can be forced through the message tool via sourceReplyDeliveryMode: "message_tool_only" (for example for Slack group/channel visible replies when configured that way). In that path, message(action="send") currently performs a required durable-send preflight that unconditionally requires reconcileUnknownSend.

The Slack adapter does not appear to expose reconcileUnknownSend, so the send is rejected before any fallback/best-effort delivery can happen:

Required durable message send is unsupported for slack: missing reconcileUnknownSend

This turns a safe source-reply delivery policy into a visible reply failure for Slack.

Environment

  • OpenClaw package: openclaw 2026.5.18
  • Runtime: macOS Darwin 25.5.0 arm64
  • Node: v24.15.0
  • Provider/channel: Slack channel / group chat thread
  • Config shape involved: Slack group/channel visible source replies routed through the message tool

Evidence from installed package

source-reply-delivery-mode-* can choose message_tool_only for group/channel chats when visible replies are configured as message_tool:

if (chatType === "group" || chatType === "channel") mode =
  (params.cfg.messages?.groupChat?.visibleReplies ?? params.cfg.messages?.visibleReplies) === "message_tool"
    ? "message_tool_only"
    : "automatic";

dist/message-*.js then derives required message-send capabilities with reconcileUnknownSend: true unconditionally:

function deriveRequiredMessageSendCapabilities(params) {
  const requirements = { reconcileUnknownSend: true };
  for (const payload of params.payloads) mergeDurableRequirements(requirements, deriveDurableFinalDeliveryRequirements({
    payload,
    replyToId: params.replyToId,
    threadId: params.threadId,
    silent: params.silent,
    payloadTransport: payloadRequiresDurablePayloadTransport(payload),
    batch: params.payloads.length > 1,
    reconcileUnknownSend: true
  }));
  return requirements;
}

When Slack cannot satisfy that capability, core throws:

throw new Error(`Required durable message send is unsupported for ${params.channel}: ${suffix}`);

The plugin SDK docs say best-effort final delivery does not require reconcileUnknownSend, but required durable final delivery does:

Best-effort final delivery does not require `reconcileUnknownSend` ...
Required durable final delivery must explicitly require `reconcileUnknownSend`.

Observed behavior

In a Slack channel/thread, a visible assistant reply that must use the message tool fails with:

Required durable message send is unsupported for slack: missing reconcileUnknownSend

Using bestEffort: true avoids this particular preflight failure and allows Slack delivery, but the source-reply contract/runtime hint does not make that automatic for the assistant. The model/tool path can therefore be instructed to send via message, while the default durable send path is impossible for Slack.

Expected behavior

One of these should happen:

  1. Slack implements/exposes reconcileUnknownSend, so required durable message sends are supported; or
  2. Slack source replies in message_tool_only mode automatically degrade to best-effort when required durability is unavailable, preserving visible delivery semantics; or
  3. Core/tool instructions make the required bestEffort: true fallback explicit for Slack adapters without reconciliation.

Actual behavior

The send is rejected by durable capability preflight, and the source reply is not posted unless the agent manually retries with bestEffort: true.

Impact

  • Slack channel/group source replies can silently fail or be blocked when the safe message_tool_only delivery policy is enabled.
  • The user sees no visible assistant reply even though the assistant may have completed the turn.
  • Agents are pushed into manual retry/fallback behavior, which is error-prone and can interact badly with duplicate/unknown-send handling.

Related issues

  • Related but not identical to #83710 (ambiguous Slack middleware errors / retries; mentions the same missing reconcileUnknownSend symptom).
  • Related family to #80715 (Slack replies composed but not posted).

Private Slack channel content is redacted; I can provide additional local session metadata if useful.

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

One of these should happen:

  1. Slack implements/exposes reconcileUnknownSend, so required durable message sends are supported; or
  2. Slack source replies in message_tool_only mode automatically degrade to best-effort when required durability is unavailable, preserving visible delivery semantics; or
  3. Core/tool instructions make the required bestEffort: true fallback explicit for Slack adapters without reconciliation.

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_tool_only source replies fail because durable send requires reconcileUnknownSend [1 pull requests, 2 comments, 2 participants]