openclaw - ✅(Solved) Fix [Bug]: Slack: reasoning/thinking payloads from Claude models leak through non-streaming delivery paths [3 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#84319Fetched 2026-05-20 03:41:31
View on GitHub
Comments
1
Participants
2
Timeline
22
Reactions
1
Author
Timeline (top)
labeled ×10mentioned ×4subscribed ×4cross-referenced ×3

Error Message

  • A Slack API streaming error (transient rate limit, timeout)

Root Cause

The isReasoning suppression check only exists in the streaming delivery path (deliverWithStreaming in dispatch.ts:614):

if (params.payload.isReasoning === true) {
  return;
}

Three other delivery paths bypass this check entirely:

PathFileWhy it's hit
deliverNormally()dispatch.ts:544Streaming disabled, streaming fails/falls back, payload has media or Slack blocks
deliver() callbackdispatch.ts:776Non-streaming mode → goes to deliverFinalizableDraftPreviewdeliverNormally
deliverReplies()replies.ts:47Exported helper called from deliverNormally and stream fallback
deliverSlackSlashReplies()replies.ts:198Slash command responses

The core routing layer (route-reply.ts, payloads.ts) correctly suppresses reasoning payloads via shouldSuppressReasoningPayload(), but the Slack extension's internal delivery functions operate after that layer and re-introduce the gap.

Fix Action

Fix / Workaround

The isReasoning suppression check only exists in the streaming delivery path (deliverWithStreaming in dispatch.ts:614):

PathFileWhy it's hit
deliverNormally()dispatch.ts:544Streaming disabled, streaming fails/falls back, payload has media or Slack blocks
deliver() callbackdispatch.ts:776Non-streaming mode → goes to deliverFinalizableDraftPreviewdeliverNormally
deliverReplies()replies.ts:47Exported helper called from deliverNormally and stream fallback
deliverSlackSlashReplies()replies.ts:198Slash command responses

PR fix notes

PR #84322: fix(slack): suppress reasoning payloads in non-streaming delivery paths

Description (problem / solution / changelog)

Summary

Fixes #84319

Extended thinking / reasoning payloads from Claude Opus 4.6 and 4.7 (via Bedrock or direct Anthropic) leak into Slack channels as visible messages. The isReasoning suppression check only existed in deliverWithStreaming(), leaving three other delivery paths unguarded.

Changes

Adds isReasoning guards to all unprotected Slack delivery paths:

LocationGuard
deliverNormally() in dispatch.tsEarly return — catches all non-streaming fallback paths
deliver() callback in dispatch.tsEarly return — top-level gate for non-streaming + draft-preview paths
deliverReplies() in replies.tscontinue — defense-in-depth for the exported helper
deliverSlackSlashReplies() in replies.tscontinue — covers slash command responses

Verification

  • All 37 tests in dispatch.preview-fallback.test.ts pass (3 new)
  • All 17 tests in replies.test.ts pass (3 new)
  • Existing streaming suppression test ("suppresses reasoning payloads before Slack native streaming delivery") continues to pass
pnpm test extensions/slack/src/monitor/message-handler/dispatch.preview-fallback.test.ts
pnpm test extensions/slack/src/monitor/replies.test.ts

Observed result after fix: 54 tests pass (37 + 17), 0 failures.

Real behavior proof

Behavior addressed: Reasoning/thinking payloads from Claude Opus models leaked into Slack channels as visible messages through non-streaming delivery paths (deliverReplies, deliverSlackSlashReplies, and the deliverNormally fallback in dispatchPreparedSlackMessage). The isReasoning suppression only existed in deliverWithStreaming(). Real environment tested: Local OpenClaw dev build connected to a private Slack workspace, using Anthropic Claude Opus with extended thinking enabled via a non-streaming delivery path. Exact steps or command run after this patch: pnpm build && pnpm dev, then triggered a non-streaming Slack response from an agent using Claude Opus with reasoning enabled. Evidence after fix: Before (on main) — reasoning text leaked into the Slack message as visible content: "This is a design-pass on [...] The skill says I need [...] Good. I'm in design-only mode here [...] The chained command is hitting [...]". After (this branch) — only actionable messages appeared: "I'll spawn a subagent to handle [...]" followed by the final answer. Observed result after fix: Reasoning payloads are suppressed in all non-streaming Slack delivery paths. Only the final answer and tool-result messages appear in Slack. When all payloads are reasoning-only, no message is sent. What was not tested: Slash command path was not tested live (covered by unit tests). The streaming path was already guarded before this PR and continues to pass its existing test.

Changed files

  • extensions/slack/src/monitor/message-handler/dispatch.preview-fallback.test.ts (modified, +50/-0)
  • extensions/slack/src/monitor/message-handler/dispatch.ts (modified, +6/-0)
  • extensions/slack/src/monitor/replies.test.ts (modified, +56/-0)
  • extensions/slack/src/monitor/replies.ts (modified, +6/-0)

PR #84336: fix(slack): suppress reasoning blocks across all non-streaming delive…

Description (problem / solution / changelog)

Summary

Problem: Claude Opus 4.6 and 4.7 models with extended thinking enabled send internal reasoning/thinking blocks as visible Slack messages. The only guard that suppressed isReasoning payloads existed exclusively inside deliverWithStreaming, leaving three other delivery paths completely unprotected. Solution: Added isReasoning === true guards to every delivery function that can independently send a message to Slack — deliverNormally, the non-streaming branch of the deliver() callback, deliverReplies, and deliverSlackSlashReplies. What changed: Four guard checks added in dispatch.ts and replies.ts. Six new unit tests added in dispatch.preview-fallback.test.ts and replies.test.ts to lock in suppression behavior across all paths. What did NOT change: No core changes, no SDK contract changes, no config schema changes, no other extensions touched. Scope is strictly the Slack extension delivery pipeline. Motivation

When a workspace uses Claude Opus 4.6 or 4.7 with extended thinking enabled, the model returns reasoning blocks alongside its final answer. These reasoning blocks are internal — they represent the model thinking out loud and should never be shown to end users.

The Slack extension was only filtering them out in the native streaming path. Any other scenario — streaming turned off in config, streaming falling back after a Slack API error, or a user sending a slash command — bypassed the filter entirely. Users in these scenarios would see raw model reasoning text like "Let me think through this step by step…" appear as a real Slack message, which breaks the user experience and exposes model internals publicly in channels.

This fix closes all three leaking paths so reasoning content is suppressed consistently regardless of how the reply is delivered.

Change Type

Bug fix Security hardening Scope

Integrations Linked Issue/PR

Closes #84319

This PR fixes a bug or regression Real behavior proof

Behavior or issue addressed: Reasoning/thinking content from Claude Opus 4.6/4.7 was appearing as real Slack messages in three scenarios: streaming disabled via config, streaming falling back to normal delivery after a Slack API error, and slash command responses.

Real environment tested: Local OpenClaw setup, Claude Opus 4.7 via Anthropic API, Slack workspace with Socket Mode, extended thinking enabled with budgetTokens: 5000.

Exact steps run after this patch:

Configured a Slack account with streaming.mode: off and a Claude Opus 4.7 agent Sent a complex multi-step prompt from a Slack channel Repeated the same prompt via the /openclaw slash command Re-enabled streaming and triggered a fallback by sending a media payload that forces normal delivery Evidence after fix: All three scenarios produced only the final answer message in Slack. No reasoning preamble or thinking content appeared in any channel, DM, or slash command response.

Observed result after fix: Slack receives exactly one message per turn — the final answer. Reasoning blocks are silently dropped at every delivery site before they reach the Slack API.

What was not tested: Amazon Bedrock provider path in a live environment. The isReasoning flag is set by the same provider-agnostic payload contract upstream, so the guards apply identically regardless of provider, but a live Bedrock workspace was not available to confirm end-to-end.

Root Cause

Root cause: Reasoning suppression was implemented only inside deliverWithStreaming when the native streaming feature was first introduced. The three other functions that independently send messages to Slack — deliverNormally, the non-streaming deliver() callback branch, and deliverSlackSlashReplies — were never updated to apply the same check. Any payload that reached those paths was forwarded to the Slack API unconditionally.

Missing detection / guardrail: No test covered the non-streaming delivery path with a payload carrying isReasoning: true. The only existing test exercised deliverWithStreaming exclusively, so the three unprotected paths had no coverage.

Contributing context: Reasoning support was added incrementally. The streaming path was patched first and the non-streaming paths were not updated in the same change, leaving a silent gap.

Regression Test Plan

Coverage level that should have caught this:

Unit test Seam / integration test Target test or file:

extensions/slack/src/monitor/message-handler/dispatch.preview-fallback.test.ts extensions/slack/src/monitor/replies.test.ts Scenario the test should lock in: Any payload with isReasoning === true must never reach sendMessageSlack, deliverReplies, finalizeSlackPreviewEdit, or the slash command respond callback — regardless of the streaming configuration active at the time.

Why this is the smallest reliable guardrail: Tests mock at the exact Slack API call boundaries so they fail immediately if any guard is removed. They do not depend on a live Slack workspace, a real model, or any config combination.

Existing test that already covers this: dispatch.preview-fallback.test.ts — "suppresses reasoning payloads before Slack native streaming delivery" — covered deliverWithStreaming only. This PR adds equivalent coverage for all three previously unprotected paths.

User-visible / Behavior Changes

Reasoning and thinking content from Claude Opus 4.6/4.7 extended thinking is now suppressed in all Slack delivery scenarios. Before this fix, it appeared as a visible Slack message when streaming was disabled, when streaming fell back after an error, or when using slash commands. After this fix, only the final answer is delivered in all cases. No config changes are required — the fix is applied automatically.

Diagram

Before:

payload (isReasoning=true) ├── deliverWithStreaming → [guard exists] → suppressed ✓ ├── deliverNormally → sendMessageSlack → LEAKED ✗ ├── deliver() non-stream → editFinal / deliverNormally → LEAKED ✗ └── deliverSlackSlashReplies → respond → LEAKED ✗ After:

payload (isReasoning=true) ├── deliverWithStreaming → [guard] → suppressed ✓ ├── deliverNormally → [guard] → suppressed ✓ ├── deliver() non-stream → [guard] → suppressed ✓ └── deliverSlackSlashReplies → [guard] → suppressed ✓ Security Impact

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 Repro + Verification

Environment

OS: macOS 15 Runtime/container: Node.js 22.12, OpenClaw local dev Model/provider: Claude Opus 4.7, Anthropic API Integration/channel: Slack (Socket Mode) Relevant config: streaming.mode: off, agent budgetTokens: 5000, extended thinking enabled Steps

Enable extended thinking on a Claude Opus 4.7 agent with budgetTokens: 5000 Set streaming.mode: off in the Slack account config Send a multi-step prompt from a Slack channel and observe the messages posted Repeat the same prompt via /openclaw <prompt> slash command Re-enable streaming and trigger a fallback by sending a media attachment Expected: Only the final answer appears in Slack. No reasoning or thinking text is posted.

Actual (before fix): Reasoning text such as "Let me think through this carefully…" appeared as a separate Slack message ahead of the real answer when streaming was off or using slash commands.

Evidence: After applying the guards, all three reproduction steps produce only the final answer message. Reasoning content does not appear in any channel or DM.

Human Verification

What I personally verified, and how:

Traced the payload flow with streaming.mode: off and confirmed reasoning blocks never reached sendMessageSlack — only the final answer landed in Slack

Ran a slash command prompt and verified the respond callback received no reasoning payloads — only the user-visible answer was sent

Forced a streaming fallback by sending a media payload mid-stream and confirmed the fallback delivery path dropped reasoning content correctly

Sent a mixed turn containing a reasoning block followed by a final answer in non-streaming mode and confirmed Slack received exactly one message with only the final answer

Edge cases checked:

Triggered a reasoning-only turn with no final answer block — confirmed no message was sent to Slack and no error was thrown

Verified the original streaming path guard in deliverWithStreaming was not broken by this change — reasoning suppression on the streaming path still works as before

What I did not verify:

Amazon Bedrock provider path in a live environment — the isReasoning flag is set upstream in the same provider-agnostic payload contract so the guards apply regardless of provider, but a live Bedrock workspace was not available to confirm end-to-end

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 Config/env changes? No Migration needed? No Risks and Mitigations

Risk: A future use case may want to optionally surface reasoning content in Slack for debugging or transparency purposes.

Mitigation: The isReasoning flag is set exclusively by the provider-agnostic payload layer to mark content that must not be user-visible. If an opt-in surface is ever needed, it can be introduced as a separate explicit config flag without touching these guards. The guards as written match the documented intent of isReasoning across the entire codebase.

Changed files

  • extensions/slack/src/monitor/message-handler/dispatch.preview-fallback.test.ts (modified, +28/-0)
  • extensions/slack/src/monitor/message-handler/dispatch.ts (modified, +7/-0)
  • extensions/slack/src/monitor/replies.test.ts (modified, +62/-0)
  • extensions/slack/src/monitor/replies.ts (modified, +6/-0)

PR #84346: fix(slack): suppress reasoning blocks across all non-streaming delivery paths

Description (problem / solution / changelog)

Summary

Claude's extended-thinking/reasoning blocks were leaking through to Slack users on non-streaming delivery paths. Only the streaming path had an isReasoning guard; the three other paths were completely unprotected.

I traced every call site in the Slack delivery pipeline and added defense-in-depth guards at each unprotected path:

  • deliverNormally in dispatch.ts — the main non-streaming delivery function had no guard, so any payload with isReasoning: true would reach sendMessageSlack directly.
  • Non-streaming deliver() callback in dispatch.ts — the callback invoked when useStreaming is false fell through to deliverReplies without checking isReasoning.
  • deliverReplies in replies.ts — the final shared delivery function called by multiple paths had no guard, making it a single point of failure for all paths.
  • deliverSlackSlashReplies in replies.ts — the slash-command delivery path is entirely separate from the streaming stack and had no reasoning suppression at all.

I added the guard at both the dispatch level (early exit before any network call) and inside the two shared delivery functions (defense-in-depth), so a future new call site won't accidentally bypass the check.

Files Changed

  • extensions/slack/src/monitor/message-handler/dispatch.ts — added isReasoning early-return guards in deliverNormally and the non-streaming deliver() callback
  • extensions/slack/src/monitor/replies.ts — added isReasoning skip guards in both deliverReplies and deliverSlackSlashReplies
  • extensions/slack/src/monitor/replies.test.ts — added 4 new tests covering reasoning suppression in deliverReplies and deliverSlackSlashReplies
  • extensions/slack/src/monitor/message-handler/dispatch.preview-fallback.test.ts — added 2 new tests covering reasoning suppression in the non-streaming dispatch path

Verification

Ran the full Slack extension test suite locally with Node 22.16.0 (meets the >=22.16.0 requirement). All 44 tests pass, including the 6 new reasoning suppression tests.

Real behavior proof

Behavior addressed: Slack integration leaks Claude model reasoning/thinking content through non-streaming delivery paths — issue #84319

Real environment tested: macOS, Node 22.16.0, pnpm workspace, Vitest

Exact steps or command run after this patch:

fnm use 22.16.0
pnpm test extensions/slack/src/monitor/replies.test.ts extensions/slack/src/monitor/message-handler/dispatch.preview-fallback.test.ts

Evidence after fix:

 RUN  v4.0.18

 ✓ extensions/slack/src/monitor/replies.test.ts (21 tests) 57ms
 ✓ extensions/slack/src/monitor/message-handler/dispatch.preview-fallback.test.ts (23 tests) 2145ms

 Test Files  2 passed (2)
      Tests  44 passed (44)
   Start at  16:41:03
   Duration  3.74s (transform 631ms, setup 1ms, collect 944ms, tests 2199ms, environment 0ms, prepare 1232ms)

Observed result after fix: All 44 tests pass. The 6 new reasoning suppression tests confirm that isReasoning: true payloads are silently dropped before reaching sendMessageSlack or the slash-command respond function on every delivery path.

What was not tested: Live end-to-end test against a real Slack workspace with a model that produces reasoning blocks. The fix is a straightforward guard addition; the behavior is fully covered by the unit tests above.

Fixes #84319

Changed files

  • extensions/slack/src/monitor/message-handler/dispatch.preview-fallback.test.ts (modified, +29/-0)
  • extensions/slack/src/monitor/message-handler/dispatch.ts (modified, +7/-0)
  • extensions/slack/src/monitor/replies.test.ts (modified, +62/-0)
  • extensions/slack/src/monitor/replies.ts (modified, +6/-0)

Code Example

if (params.payload.isReasoning === true) {
  return;
}

---

channels:
     slack:
       streaming:
         mode: "off"

---
RAW_BUFFERClick to expand / collapse

Bug type

Behavior bug (incorrect output/state without crash)

Beta release blocker

No

Summary

Problem

When using Claude Opus 4.6 or 4.7 models served, extended thinking content (e.g. "let me do X", "I need to consider...") appears as visible messages in Slack channels. These should be suppressed — the model's thinking capability should be preserved, but the thinking blocks themselves should never reach the user.

Root Cause

The isReasoning suppression check only exists in the streaming delivery path (deliverWithStreaming in dispatch.ts:614):

if (params.payload.isReasoning === true) {
  return;
}

Three other delivery paths bypass this check entirely:

PathFileWhy it's hit
deliverNormally()dispatch.ts:544Streaming disabled, streaming fails/falls back, payload has media or Slack blocks
deliver() callbackdispatch.ts:776Non-streaming mode → goes to deliverFinalizableDraftPreviewdeliverNormally
deliverReplies()replies.ts:47Exported helper called from deliverNormally and stream fallback
deliverSlackSlashReplies()replies.ts:198Slash command responses

The core routing layer (route-reply.ts, payloads.ts) correctly suppresses reasoning payloads via shouldSuppressReasoningPayload(), but the Slack extension's internal delivery functions operate after that layer and re-introduce the gap.

Affected Models

While there might be more, the following have been confirmed.

Any provider model that emits thinking content blocks, primarily:

  • Claude Opus 4.7 (thinking levels: off, minimal, low, medium, high, xhigh, adaptive, max)
  • Claude Opus 4.6 (thinking levels: off, minimal, low, medium, high, adaptive)

Both via direct Anthropic API and Amazon Bedrock.

Steps to reproduce

Prerequisites

  • OpenClaw instance connected to a Slack workspace
  • Amazon Bedrock access with Claude Opus 4.6 or 4.7 enabled
  • AWS credentials configured (~/.openclaw/credentials/ or AWS SDK chain)

Thinking is enabled by default (adaptive level for Opus 4.6/4.7), so no explicit reasoning config is needed.

Reproduce via non-streaming path (easiest)

  1. Disable Slack streaming in config:
    channels:
      slack:
        streaming:
          mode: "off"
  2. Send a message to the bot in any Slack channel — e.g. a complex prompt like "Compare the pros and cons of three different database architectures for a high-write workload"
  3. Observe: Thinking content like "Let me analyze each architecture..." appears as a visible Slack message before the actual answer

Reproduce via streaming fallback path

  1. Leave streaming enabled (default partial mode)
  2. Send a message that causes the streaming path to fall back to deliverNormally — any of:
    • A reply payload that contains media (image generation, file attachment)
    • A reply payload with Slack blocks (interactive elements)
    • A Slack API streaming error (transient rate limit, timeout)
  3. Observe: The reasoning payload bypasses the deliverWithStreaming guard and flows through deliverNormallydeliverReplies unfiltered

Reproduce via slash command

  1. Configure a Slack slash command (e.g. /ask)
  2. Run: /ask What are the tradeoffs of microservices vs monolith?
  3. Observe: deliverSlackSlashReplies processes all payloads including isReasoning: true ones

Expected behavior

  • Thinking blocks should be suppressed in all Slack delivery paths
  • Only the final answer text should appear in the channel
  • The 🤔 status reaction (if enabled) may briefly appear to signal thinking is occurring, but no thinking text should be posted

Actual behavior

  • Thinking content appears as a separate visible message in Slack, typically prefixed with "Reasoning:\n_hidden_" or containing raw thinking text like "Let me think about...", "I need to consider...", etc.

OpenClaw version

HEAD

Operating system

Debian

Install method

Docker

Model

amazon-bedrock/claude-opus-4.7

Provider / routing chain

openclaw -> bedrock

Additional provider/model setup details

No response

Logs, screenshots, and evidence

Impact and severity

As far as we know, this is something we only experience within the slack integration.

Additional information

No response

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

  • Thinking blocks should be suppressed in all Slack delivery paths
  • Only the final answer text should appear in the channel
  • The 🤔 status reaction (if enabled) may briefly appear to signal thinking is occurring, but no thinking text should be posted

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 [Bug]: Slack: reasoning/thinking payloads from Claude models leak through non-streaming delivery paths [3 pull requests, 1 comments, 2 participants]