Skip to content

Fix Slack thread streaming missing recipient IDs (#20337)#20377

Closed
AsserAl1012 wants to merge 1 commit intoopenclaw:mainfrom
AsserAl1012:fix/20337-slack-stream-recipient-team
Closed

Fix Slack thread streaming missing recipient IDs (#20337)#20377
AsserAl1012 wants to merge 1 commit intoopenclaw:mainfrom
AsserAl1012:fix/20337-slack-stream-recipient-team

Conversation

@AsserAl1012
Copy link

@AsserAl1012 AsserAl1012 commented Feb 18, 2026

Summary

  • Problem: Slack thread replies could silently fail when Slack native streaming is enabled, with gateway logs showing missing_recipient_team_id and no assistant reply posted to the triggering thread.
  • Why it matters: Threaded workflows become unreliable (runs complete but users see no response), which breaks Slack usage and erodes trust.
  • What changed: Slack stream start now includes recipient_team_id and recipient_user_id; streaming is gated to only run when streamingEnabled, thread_ts, ctx.teamId, and message.user are present; stop-stream errors are caught/logged to avoid crashing/failing delivery.
  • What did NOT change (scope boundary): No changes to Slack non-thread delivery semantics; fallback non-streaming delivery behavior remains intact; no changes to other channel integrations.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • 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

User-visible / Behavior Changes

  • Slack thread mentions now reliably receive streamed assistant replies in the same thread when Slack native streaming is enabled and recipient IDs are available.
  • If recipient IDs are missing, OpenClaw will skip native streaming and fall back to the normal (non-streaming) reply path instead of failing with missing_recipient_team_id.

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)
  • If any Yes, explain risk + mitigation: N/A

Repro + Verification

Environment

  • OS: Windows (dev), reported issue on Debian GNU/Linux 12 (arm64)
  • Runtime/container: Node.js + pnpm
  • Model/provider: N/A
  • Integration/channel (if any): Slack (Socket Mode + native streaming)
  • Relevant config (redacted): channels.slack.mode=socket, streaming enabled (default), thread messages with thread_ts

Steps

  1. Run OpenClaw gateway with Slack Socket Mode enabled and Slack streaming enabled.
  2. In a Slack channel where the bot is active, start or reply in a thread and mention the bot (e.g. @openclaw ...).
  3. Observe OpenClaw processes the run; verify the assistant reply is posted into the same Slack thread.

Expected

  • Assistant reply posts into the same Slack thread; no missing_recipient_team_id errors from slack-stream.

Actual

  • Run completes but no thread reply is posted; logs show slack-stream errors missing_recipient_team_id.

Evidence

Attach at least one:

  • Failing test/log before + passing after

  • Added unit tests:
    - src/slack/streaming.test.ts asserts chatStream called with recipient_team_id and recipient_user_id, and stopSlackStream swallows stop failures.
    - src/slack/monitor/message-handler/dispatch.streaming.test.ts covers gating when teamId or userId is missing.

  • Trace/log snippets

  • Screenshot/recording

  • Perf numbers (if relevant)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios: Unit tests for streaming start parameters (recipient_team_id, recipient_user_id) and defensive stop behavior.
    Gating logic: streaming disabled when recipient IDs missing; fallback delivery remains reachable.

  • Edge cases checked:
    - Missing ctx.teamId ⇒ streaming is skipped.
    - Missing message.user ⇒ streaming is skipped.
    - Stream stop failure ⇒ caught/logged, no crash/throw path.

  • What you did not verify: Full end-to-end Slack thread streaming on a live Slack workspace in Socket Mode (left to CI / maintainers to validate in real environment).

Compatibility / Migration

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

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: Disable Slack streaming: openclaw config set channels.slack.streaming false and restart gateway, or revert this PR.

  • Files/config to restore: src/slack/streaming.ts - src/slack/monitor/message-handler/dispatch.ts

  • Known bad symptoms reviewers should watch for: Slack thread replies not appearing when streaming is enabled -- Reappearance of missing_recipient_team_id errors in slack-stream logs

Risks and Mitigations

List only real risks for this PR. Add/remove entries as needed. If none, write None.

  • Risk: Streaming could be skipped more often if recipient IDs are unavailable in some Slack event variants.
    • Mitigation: Explicit gating ensures we fall back to non-streaming delivery rather than failing silently; behavior remains correct and safer than attempting a stream without required fields.

AI Usage Disclosure

  • AI-assisted: Yes (GPT/Codex)
  • Used for: implementing change + adding regression tests
  • Human verification: ran checks/tests listed above and reviewed logic for scope/behavior

Greptile Summary

This PR fixes Slack thread streaming failures caused by missing recipient_team_id and recipient_user_id when calling chat.startStream. The fix adds these required fields to startSlackStream, gates native streaming on their presence via the new shouldUseSlackNativeStreaming function, and wraps stopSlackStream in a try/catch to prevent unhandled rejections from disrupting delivery.

  • startSlackStream now accepts and forwards recipientTeamId and recipientUserId to client.chatStream()
  • New shouldUseSlackNativeStreaming gating function ensures streaming is only attempted when all required IDs are present; otherwise falls back to normal (non-streaming) delivery
  • stopSlackStream now catches and logs errors instead of throwing, preventing delivery disruptions
  • Parameter channel renamed to channelId for clarity in StartSlackStreamParams
  • New unit tests cover recipient ID gating and defensive stop behavior
  • Minor style note: the outer try/catch around stopSlackStream in dispatch.ts is now dead code since stopSlackStream internally swallows errors

Confidence Score: 4/5

  • This PR is safe to merge — it fixes a real bug with proper fallback behavior and adds no new risk.
  • The changes are well-scoped, correctly address the missing recipient ID issue, and include defensive fallbacks. The only deductions are for minor dead code (redundant guard and dead try/catch) and a small test coverage gap (no positive test case for the gating function). No functional risks identified.
  • No files require special attention. The core logic in src/slack/monitor/message-handler/dispatch.ts has minor dead code but no functional issues.

Last reviewed commit: 7b28b33

(3/5) Reply to the agent's comments like "Can you suggest a fix for this @greptileai?" or ask follow-up questions!

@openclaw-barnacle openclaw-barnacle bot added channel: slack Channel integration: slack size: S labels Feb 18, 2026
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +211 to +218
if (!recipientTeamId || !recipientUserId) {
logVerbose(
"slack-stream: missing recipient ids for stream start, falling back to normal delivery",
);
streamFailed = true;
await deliverNormally(payload, streamThreadTs);
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant recipient ID guard (defense-in-depth)

This guard can never trigger at runtime. deliverWithStreaming is only called when useStreaming is true (line 249), and useStreaming is already gated by shouldUseSlackNativeStreaming which checks both recipientTeamId and recipientUserId (lines 167-172). If the IDs are missing, useStreaming will be false and this code path is never reached.

This isn't a bug — keeping it as defense-in-depth is a valid choice — but it's worth noting that TypeScript narrowing won't help downstream code here since the variables are already narrowed by the outer gate. If the intent is belt-and-suspenders, a brief comment explaining that would help future readers.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/slack/monitor/message-handler/dispatch.ts
Line: 211-218

Comment:
**Redundant recipient ID guard (defense-in-depth)**

This guard can never trigger at runtime. `deliverWithStreaming` is only called when `useStreaming` is `true` (line 249), and `useStreaming` is already gated by `shouldUseSlackNativeStreaming` which checks both `recipientTeamId` and `recipientUserId` (lines 167-172). If the IDs are missing, `useStreaming` will be `false` and this code path is never reached.

This isn't a bug — keeping it as defense-in-depth is a valid choice — but it's worth noting that TypeScript narrowing won't help downstream code here since the variables are already narrowed by the outer gate. If the intent is belt-and-suspenders, a brief comment explaining that would help future readers.

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 18, 2026

Additional Comments (1)

src/slack/monitor/message-handler/dispatch.ts
Dead try/catch after stopSlackStream change

Since stopSlackStream now internally catches and swallows its own errors (see streaming.ts:138-143), this outer try/catch will never fire — stopSlackStream always resolves successfully. This isn't a bug, but it is dead code that may mislead future readers into thinking stopSlackStream can throw. Consider removing the try/catch wrapper here:

  const finalStream = streamSession as SlackStreamSession | null;
  if (finalStream && !finalStream.stopped) {
    await stopSlackStream({ session: finalStream });
  }
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/slack/monitor/message-handler/dispatch.ts
Line: 424-429

Comment:
**Dead try/catch after `stopSlackStream` change**

Since `stopSlackStream` now internally catches and swallows its own errors (see `streaming.ts:138-143`), this outer `try/catch` will never fire — `stopSlackStream` always resolves successfully. This isn't a bug, but it is dead code that may mislead future readers into thinking `stopSlackStream` can throw. Consider removing the try/catch wrapper here:

```suggestion
  const finalStream = streamSession as SlackStreamSession | null;
  if (finalStream && !finalStream.stopped) {
    await stopSlackStream({ session: finalStream });
  }
```

How can I resolve this? If you propose a fix, please make it concise.

@mattmiddlesworth
Copy link

watching for when this PR gets merged as I'm affected by it

@vincentkoc
Copy link
Member

Thanks for the earlier contribution. Closing this as duplicate of #20988. Your work is credited in the final fix path; #20988 includes the same recipient-ID streaming fix and also adds the block-streaming pipeline correction needed for end-to-end delivery. If this is a mistake, please let me know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: slack Channel integration: slack size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Slack thread replies can fail when streaming is enabled (missing_recipient_team_id) and no reply is posted

3 participants