Skip to content

Slack DM streaming replies are dropped — stream() raises for empty thread_ts but _handle_stream never falls back #94

@tony-chinchill-ai

Description

@tony-chinchill-ai

Summary

Native Slack streaming silently drops the reply for top-level DMs. The agent runs, produces text, and nothing reaches the user. Channel @-mentions and threaded DM replies are unaffected.

Root cause

  1. _handle_message_event (src/chat_sdk/adapters/slack/adapter.py) encodes a top-level DM thread id with an empty thread_ts — intentional, so open_dm subscriptions match incoming DMs.
  2. Draft PR fix(adapters): bug-fix sweep from upstream 4.27.0 (slack/discord/telegram) #89 makes SlackAdapter.stream() raise ValidationError for an empty thread_ts instead of passing it to chat.startStream, which rejects it.
  3. Thread._handle_stream (src/chat_sdk/thread.py) calls adapter.stream() with no try/except. Its _fallback_stream post+edit path is reached only when the adapter has no stream method at all — not when stream() raises.

Net effect: after PR #89 a DM streaming reply fails clean instead of failing confusingly, but it is still never delivered.

Proposed fix

For an empty thread_ts, SlackAdapter.stream() should accumulate the stream and post_message once instead of raising. DMs degrade to a single non-streamed message; channels keep native streaming. This can replace the raise introduced in draft PR #89:

decoded = self.decode_thread_id(thread_id)
thread_ts = decoded.thread_ts or None
if not thread_ts:
    accumulated = ""
    async for chunk in text_stream:
        if isinstance(chunk, str):
            accumulated += chunk
        elif isinstance(chunk, dict) and chunk.get("type") in ("text-delta", "markdown_text"):
            accumulated += chunk.get("text", "")
        elif getattr(chunk, "type", None) == "markdown_text":
            accumulated += getattr(chunk, "text", "")
    return await self.post_message(thread_id, PostableMarkdown(markdown=accumulated))

post_message already normalizes thread_ts or None, so an empty DM thread_ts posts as a normal message (Slack accepts that for chat.postMessage; only chat.startStream requires one).

Note

Production-tested: chinchill-api currently runs exactly this as a MultiTenantSlackAdapter subclass override. Folding it into the SDK lets that override be deleted.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions