Skip to content

Slack native streaming bypasses outgoing @name mention resolution #754

Description

@alvarosevilla95

Bug Description

The Slack adapter applies outgoing @name mention resolution when a streamed response uses the post-and-edit fallback, but not when it uses Slack native streaming.

As a result, identical streamed content behaves differently depending on the nativeStreaming option. With native streaming disabled, a uniquely cached @alice is rewritten to Slack's <@U123> form and renders as a real mention. With native streaming enabled (the default), @alice remains plain text and does not mention or notify the user.

Steps to Reproduce

  1. Configure a Slack Chat instance and receive a message from a user whose unique display name is alice, allowing the adapter to populate its user-name and thread-participant caches.
  2. In the message handler, stream a response containing @alice.
  3. Run with nativeStreaming: true and observe that @alice remains plain text.
  4. Run the same response with nativeStreaming: false and observe that it renders as a native Slack mention.

Expected Behavior

Outgoing mention resolution should be consistent across Slack streaming modes. A uniquely resolvable cached @alice should become a native Slack mention whether the response uses native streaming or the post-and-edit fallback.

The existing ambiguity behavior should remain unchanged: participant disambiguation should be used when possible, and unresolved or ambiguous names should remain plain text.

Mention resolution should also work when a mention spans source chunks, for example "@ali" followed by "ce".

Actual Behavior

The post-and-edit path rewrites @alice to <@U123>, while the native streaming path appends @alice directly as markdown_text. The native-streamed message therefore displays the name without creating a Slack mention.

Code Sample

import { createSlackAdapter } from "@chat-adapter/slack";
import { createMemoryState } from "@chat-adapter/state-memory";
import { Chat } from "chat";

const bot = new Chat({
  userName: "mybot",
  adapters: {
    slack: createSlackAdapter({
      botToken: process.env.SLACK_BOT_TOKEN!,
      signingSecret: process.env.SLACK_SIGNING_SECRET!,
      nativeStreaming: process.env.NATIVE_STREAMING !== "false",
    }),
  },
  state: createMemoryState(),
});

bot.onNewMention(async (thread, message) => {
  await thread.subscribe();

  await thread.post(
    (async function* () {
      yield `Thanks, @${message.author.userName}`;
    })()
  );
});

Chat SDK Version

4.35.0

Node.js Version

v24.18.0

Platform Adapter

Slack

Operating System

macOS

Additional Context

This appears to come from a path difference in SlackAdapter:

  • postMessage and editMessage call resolveMessageMentions, which delegates to resolveOutgoingMentions.
  • stream sends committed renderer deltas directly through streamer.append({ markdown_text: delta }) without applying the outgoing mention resolver.

Because native streaming is enabled by default, the default mode silently loses behavior that is present in the fallback mode. Resolving the renderer's committed text before calculating/appending each delta may provide a chunk-safe place to apply the existing resolution semantics, but the issue does not depend on a particular implementation.

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