vercel / vercel/chat

Slack: inbound bold conversion doubles literal asterisks in prose and inside inline code

Open
#954 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
TypeScript
Stars
2.4k
Forks
314
Avg merge
1d 18h
Merged PRs (30d)
63

Description

Bug Description

slackMrkdwnToMarkdown converts Slack bold to markdown bold with

markdown.replace(/(?<![_*\\])\*([^*\n]+)\*(?![_*])/g, "**$1**");

The pattern matches any two asterisks on the same line, whatever surrounds them. A message that uses * as a multiplication sign or a wildcard has each matched asterisk doubled. The rule also runs over inline code spans, so the contents of `…` are changed.

Slack's own renderer does not show these messages as bold: its mrkdwn bold needs a non-space character directly inside each marker.

Steps to Reproduce
  1. In Slack, send the bot cost is 2 * 3 seats and 4 * 5 licenses, or a message with an asterisk inside inline code such as run `rm -rf *` then 5 * 3.
  2. Read message.text and stringifyMarkdown(message.formatted) in the handler, or run the code sample below, which calls the same converter the adapter uses.
Expected Behavior

Both messages come through with their asterisks unchanged and no strong node. this is *bold* text still converts to **bold**.

Actual Behavior
Input (event.text) message.text stringifyMarkdown
cost is 2 * 3 seats and 4 * 5 licenses cost is 2 ** 3 seats and 4 ** 5 licenses cost is 2 \*\* 3 seats and 4 \*\* 5 licenses
run `rm -rf *` then 5 * 3 run rm -rf ** then 5 ** 3 run `rm -rf **` then 5 \*\* 3

In the second row the match starts at the asterisk inside the code span and ends at the one outside it, so the code span's content changes from rm -rf * to rm -rf **.

Code Sample
import { SlackFormatConverter } from "@chat-adapter/slack";
import { stringifyMarkdown, toPlainText } from "chat";

const converter = new SlackFormatConverter();
for (const input of ["cost is 2 * 3 seats and 4 * 5 licenses", "run `rm -rf *` then 5 * 3"]) {
  const ast = converter.toAst(input);
  console.log(toPlainText(ast));
  console.log(stringifyMarkdown(ast));
}
Chat SDK Version

4.30.0

Node.js Version

22.21.1

Platform Adapter

Microsoft Teams, Slack

Operating System

Linux

Additional Context

Possible approaches.

  • Require a non-space character after the opening asterisk and before the closing one: /(?<![_*\\])\*(\S(?:[^*\n]*\S)?)\*(?![_*])/g. On the inputs above it leaves both messages unchanged and still converts *bold*, *a* and *b c*, and *x*. The strikethrough rule (~…~) has the same shape and would take the same change.
  • Skip inline code spans. 4.40.0 already splits the input on code fences (convertMrkdwnWithCodeFences) so fenced code is not rewritten; inline `…` spans still are.
  • When the event carries rich_text blocks, bold is unambiguous there (style.bold on the text element), so reading formatting from blocks would avoid the regex for user-typed messages.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at SlackFormatConverter and the slackMrkdwnToMarkdown conversion path, then run the provided TypeScript sample with the multiplication, inline-code, and bold inputs. Compare toPlainText and stringifyMarkdown output with the expected behavior; the work is done when literal asterisks and inline-code contents remain unchanged while genuine Slack bold still converts to Markdown strong text.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.