fix(slack): read mentions from message content - #947
Merged
Merged
Conversation
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
privatenumber
added this pull request to stack #948
September 16, 2026 11:41
privatenumber
force-pushed
the
fix/slack-structured-mentions
branch
from
September 16, 2026 13:21
559ad23 to
b2d05aa
Compare
bensabic
added a commit
that referenced
this pull request
Sep 17, 2026
## Problem Chat SDK decides whether to call `onNewMention` from `message.isMention`, and adapters are expected to set that flag from platform-specific content. Core then overrides a negative result: ```ts message.isMention = message.isMention || this.detectMention(adapter, message); ``` An adapter can inspect content that plain text cannot. Slack, for example, distinguishes a [user mention](https://docs.slack.dev/reference/block-kit/block-elements/user-element/) from a [code-styled text element](https://docs.slack.dev/reference/block-kit/block-elements/text-element/), even when both contain the same characters. With `||`, that distinction is lost. An adapter that reports `isMention: false` for a code sample is overridden when text detection finds `@username` in the flattened message, and the mention handler runs for a message that mentions nobody. [#947](#947) depends on this behavior: without it, the Slack adapter cannot keep literal code out of mention routing. ## Changes `message.isMention` is now a three-state value. | Adapter result | Core behavior | | --- | --- | | `true` | Keep the mention | | `false` | Keep the non-mention | | `undefined` | Match `@username` in the message text | An adapter that reads structured content reports `true` or `false`; one that needs text matching leaves the flag unset. The same rule applies to messages skipped while draining a queue. Ordinary Linear comments now leave the flag unset so `@displayName` detection in a comment body keeps working. Adapter authors that previously returned `false` to mean "not detected" should return `undefined` instead. The adapter-building docs now state this contract, which the previous docs left implicit by only showing the `true` case. Changed files: - [packages/chat/src/chat.ts](https://github.com/vercel/chat/blob/35c37505/packages/chat/src/chat.ts): fall back to text detection only when the adapter reports nothing - [packages/chat/src/message.ts](https://github.com/vercel/chat/blob/35c37505/packages/chat/src/message.ts): document the three states - [packages/adapter-linear/src/index.ts](https://github.com/vercel/chat/blob/35c37505/packages/adapter-linear/src/index.ts): leave ordinary comments unset - [packages/chat/src/chat.test.ts](https://github.com/vercel/chat/blob/35c37505/packages/chat/src/chat.test.ts): cover a definitive non-mention, a definitive mention, and a skipped queued message that must stay a non-mention - [packages/adapter-linear/src/index.test.ts](https://github.com/vercel/chat/blob/35c37505/packages/adapter-linear/src/index.test.ts): pin the Linear fallback contract, where an ordinary comment stays undetermined and an agent session comment is a mention - [apps/docs/content/docs/contributing/building.mdx](https://github.com/vercel/chat/blob/35c37505/apps/docs/content/docs/contributing/building.mdx) and [threads-messages-channels.mdx](https://github.com/vercel/chat/blob/35c37505/apps/docs/content/docs/threads-messages-channels.mdx): state the adapter contract ## Related PRs - [#947](#947) builds on this contract to keep literal Slack code references out of mention routing --------- Signed-off-by: Hiroki Osame <hiroki.osame@gmail.com> Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com> Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
Signed-off-by: Hiroki Osame <hiroki.osame@gmail.com>
Signed-off-by: Hiroki Osame <hiroki.osame@gmail.com>
Signed-off-by: Hiroki Osame <hiroki.osame@gmail.com>
Signed-off-by: Hiroki Osame <hiroki.osame@gmail.com>
bensabic
force-pushed
the
fix/slack-structured-mentions
branch
from
September 17, 2026 08:08
b2d05aa to
6cf8b93
Compare
- Trust `app_mention` when the content never shows the known bot id, so an id Slack reports under another form (Enterprise Grid `W…`) still routes as a mention - Distinguish a literal bot reference (code or display text) from no reference at all when classifying - Render `raw_text` table cells literally, matching how Slack displays them and how they are classified - Classify the bot's mention in the synchronous `parseMessage` path so pre-edit snapshots agree with edited messages - Match structured `user` elements against the bot id case-insensitively, like the token pattern - Compute attachment content once per message and share it between classification and rendering - Add `maskCodeSpans` to `@chat-adapter/shared`, built on the existing code-span scanner, and use it in place of the Slack-local regex masker - Add a captured `app_mention` fixture with a code-styled `user` element to `sample-messages.md` - Add tests for the Enterprise Grid id case, code-styled `user` elements, sync classification, lowercase bot ids, and literal `raw_text` rendering - Update the Slack changeset and add one for `@chat-adapter/shared` Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
bensabic
approved these changes
Sep 17, 2026
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Problem
Chat SDK routes a message to
onNewMentionwhen the bot is mentioned, and the Slack adapter decided that from the raw text: it scanned for the bot's id and marked everyapp_mentionevent as a mention.Slack models those two cases differently. A user mention "renders as a mention of a user", while a text element with
style.codedisplays literal code. Both can carry the same id characters:{ "type": "text", "text": "<@UBOT123>", "style": { "code": true } }{ "type": "user", "user_id": "UBOT123" }Only the second is a mention. Because the adapter also searched the text, a message that only documents the bot was treated as an invocation:
app_mention.isMention, and the mention handler ran.Changes
The adapter now classifies the content the way Slack renders it, instead of scanning every string for the bot's id.
userelement for the bot outside code is a mention.<@U…>token in a text object explicitly typedmrkdwnis a mention.style.code) andpreformattedelements are not.textelement, a link label, and araw_texttable cell are display text. A token there stays literal, so it is not a mention.When a message carries blocks, those blocks model its body and the flattened
textfield is not consulted, because it loses the code/literal distinction. Attachment blocks are authoritative for their attachment, so an attachment's legacy fallback cannot add mention evidence. Blocks and mrkdwn content are both traversed, so asectionblock mention is still found.The unconditional
app_mentionoverride is gone. A fully inspected message with no mention reportsisMention: false, even when the bot's display name appears as plain text, which #946 preserves. If the adapter cannot identify the bot (botUserIdunresolved), anapp_mentionevent is still trusted and other messages stay undetermined.The routing test in packages/adapter-slack/src/index.test.ts sends a signed webhook through the real adapter and a real
Chatinstance. Against the pre-fix adapter it fails because the mention handler runs; with the fix it passes. Seven cases were confirmed to fail before the element-type fix, coveringraw_textcells, rich-text text elements, link labels,plain_textblocks, and attachment fallbacks.The Slack integration suite also delivered events as
text: "@botname ...". Slack mentions are<@U…>tokens, so those fixtures were corrected to the documented syntax; the operations those tests own (subscriptions, edits, reactions, uploads) are unchanged.Changed files:
<@U…>syntaxRelated PRs
Chatre-detects the mention from the flattened text and the false positive returns