Skip to content

Slack message.text drops the first line of code blocks that start immediately after the opening fence #842

Description

@akkadaska

Bug Description

On Slack, message.text drops the first line of a fenced code block when the code starts immediately after the opening triple backticks.

Slack represents a code block like this in an incoming message event:

```first line
second line
```

SlackFormatConverter#toAst normalizes the Slack mrkdwn and then passes it directly to the CommonMark parser:

toAst(mrkdwn: string): Root {
  return parseMarkdown(slackMrkdwnToMarkdown(mrkdwn));
}

However, Slack and CommonMark interpret the opening line differently. Slack treats first line as code block content, while CommonMark treats it as the fenced code block's info string. The resulting mdast code node is equivalent to:

{
  type: "code",
  lang: "first",
  meta: "line",
  value: "second line",
}

The Slack adapter then derives message.text with toPlainText(formatted). For a code node, plain-text extraction returns only node.value, so the info string is omitted and the first line disappears:

text: toPlainText(formatted),

As a result, the example above produces message.text === "second line" instead of "first line\nsecond line". message.formatted is also incorrect because the first line is stored as lang / meta rather than code content.

Steps to Reproduce

  1. Create a Chat instance with a Slack adapter.
  2. Send the bot a Slack message containing a fenced code block whose content begins immediately after the opening triple backticks:
    ```first line
    second line
    ```
    
  3. Inspect the incoming Message in a handler:
    bot.onNewMention((_thread, message) => {
      console.log(message.text);
      console.log(message.formatted);
    });
  4. Observe that message.text contains only second line, while the first line appears as the code node's lang / meta fields.

Expected Behavior

Slack code block content should be preserved verbatim in both message.text and message.formatted:

message.text;
// "first line\nsecond line"

message.formatted.children[0];
// {
//   type: "code",
//   lang: null,
//   meta: null,
//   value: "first line\nsecond line",
// }

Actual Behavior

The first line is interpreted as a CommonMark info string and omitted from message.text:

message.text;
// "second line"

message.formatted.children[0];
// {
//   type: "code",
//   lang: "first",
//   meta: "line",
//   value: "second line",
// }

Code blocks with a newline immediately after the opening fence are unaffected:

```
first line
second line
```

Code Sample

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

const bot = new Chat({
  adapters: [createSlackAdapter({ /* ... */ })],
});

bot.onNewMention((_thread, message) => {
  console.log(message.text);
  // Slack input:
  //  line
  // second line
  // 
  //
  // Actual:   "second line"
  // Expected: "first line\nsecond line"
});

Chat SDK Version

4.38.1

Node.js Version

24.18.0

Platform Adapter

Slack

Operating System

macOS

Additional Context

The existing Slack tests cover mentions inside triple-backtick code blocks and fenced code blocks surrounded by mentions, but they do not assert parsing when code content starts on the same line as the opening fence.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions