Skip to content

Fix broken markdown rendering in AI Slack responses#163

Open
bajman wants to merge 1 commit intodev-chat:masterfrom
bajman:fix/slack-markdown-rendering
Open

Fix broken markdown rendering in AI Slack responses#163
bajman wants to merge 1 commit intodev-chat:masterfrom
bajman:fix/slack-markdown-rendering

Conversation

@bajman
Copy link

@bajman bajman commented Mar 15, 2026

Summary

  • LLM responses containing standard markdown (### headers, **bold**, * bullet lists) were rendering as raw text in Slack because Slack uses its own mrkdwn format
  • The existing markdownToSlackMrkdwn() converter was incomplete (missing bullet lists, regex collisions) and not applied to all code paths (Gemini path had zero conversion)
  • Adopts Slack's new markdown block type (released March 6, 2026), which natively translates standard markdown server-side — designed specifically for LLM output
  • Applied across all four AI text paths: /ai/text, /ai/prompt-with-history, @moonbeam, and redeployMoonbeam
  • Removes the manual markdownToSlackMrkdwn() converter and getChunks utility (net -520 lines)
  • Upgrades @slack/web-api v6 → v7.15.0 with associated breaking change fixes (users.list args, as_user deprecation, files.uploadfilesUploadV2)

Test plan

  • Verify /prompt slash command renders headers, bold, italic, and bullet lists correctly in Slack
  • Verify /ai/text slash command renders formatted AI responses correctly
  • Verify @moonbeam mention responses render with proper formatting
  • Verify Moonbeam deploy message in #muzzlefeedback renders quote correctly
  • Run npx jest in packages/backend — all tests related to changed files pass

🤖 Generated with Claude Code

LLM responses containing standard markdown (### headers, **bold**,
* bullet lists) were rendering as raw text in Slack because Slack
uses its own mrkdwn format. The existing markdownToSlackMrkdwn()
converter was incomplete and not applied to all code paths.

Resolves this by adopting Slack's new markdown block type, which
natively translates standard markdown server-side. Applied across
all four AI text paths: /ai/text, /ai/prompt-with-history,
@moonbeam, and redeployMoonbeam. Removes the manual converter
and getChunks utility.

Also upgrades @slack/web-api v6 → v7.15.0 with associated
breaking change fixes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
token: muzzleToken,
channel,
ts,
as_user: true,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty sure we need this. Bots can't delete messages, only bots acting as the user who has the entitlement to do this, can. Since I am the owner/admin, I have this entitlement + I gave her permission to act on my behalf.


public getAllUsers(): Promise<WebAPICallResult> {
return this.web.users.list();
return this.web.users.list({});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary param, remove.

public sendMessage(channel: string, text: string, blocks?: Block[] | KnownBlock[]): Promise<WebAPICallResult> {
const token: string | undefined = process.env.MUZZLE_BOT_USER_TOKEN;
const postRequest: ChatPostMessageArguments = {
const postRequest = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add the type back. We want type safety and as ChatMessageArguments is not that.

"@types/jest-when": "^2.7.0",
"@types/lolex": "^3.1.1",
"@types/node": "^12.12.56",
"@types/node": "^18.0.0",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Realistically, this should be bumped to 20 since that is the version of node we end up deploying based on what is present in the docker image.

if (blocks) {
postRequest.blocks = blocks;
}
...(blocks && { blocks }),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Syntax is weird, change to blocks: blocks ?? undefined

postRequest.blocks = blocks;
}
...(blocks && { blocks }),
} as ChatPostMessageArguments;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't do this as type coercion. Make the type right from the get.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants