Polishing the Slack Experience
Strip mention tags, convert standard markdown to Slack formatting, and add a thinking indicator. Three improvements that turn a working integration into one people actually want to use.
Three problems to solve
The basic Slack integration works. But three things make the experience rough in practice:
1. The agent sees the raw @BotName tag in the message text instead of just the question 2. Responses come back in standard markdown — which Slack does not render correctly 3. There is no feedback while the agent is working — the user just waits in silence
These are not cosmetic. The tag in the message text affects the agent's interpretation. Badly formatted responses look broken. No feedback makes users think the bot is offline. All three are worth fixing before anyone else uses the bot.
Fix 1: Strip the mention tag
When a user types @ClaudeAgent what's our Q2 budget?, the agent receives the full string including the tag markup. Clean it before passing to the agent:
Create src/helpers.ts:
export function stripMentionText(text: string): string {
return text.replace(/<@[A-Z0-9]+>/g, "").trim();
}
Use it in your event handlers:
const userMessage = stripMentionText(event.text ?? "");
const { response } = await agentChat(userMessage);
Fix 2: Slack markdown formatting
Slack uses a simplified version of markdown. The key differences:
| Standard markdown | Slack markdown | |
|---|---|---|
bold | bold | |
italic | italic | |
link | `| text>` | |
`code` | `code` (same) | |
# Heading | Heading (bold) |
Handle this in two layers. First, update the system prompt in agent.ts to request Slack-formatted output:
const SYSTEM_PROMPT = `You are a helpful assistant deployed in Slack.
Format all responses using Slack markdown:
- Bold: *text* (single asterisks)
- Italic: _text_ (underscores)
- Code: \`code\` or \`\`\`code block\`\`\`
- Lists: - item (dash + space)
Do not use standard markdown heading syntax (#, ##).`;
Second, add a conversion function as a safety net in helpers.ts:
export function markdownToSlack(text: string): string {
return text
.replace(/\*\*(.*?)\*\*/g, "*$1*") // **bold** → *bold*
.replace(/__(.*?)__/g, "_$1_") // __italic__ → _italic_
.replace(/^#{1,6}\s+(.+)$/gm, "*$1*"); // headings → bold
}
Apply it when posting the response:
await app.client.chat.update({
token: process.env.SLACK_BOT_TOKEN,
channel: event.channel,
ts: thinkingMessage.ts!,
text: markdownToSlack(response),
});
Fix 3: Thinking indicator
Post a placeholder message immediately when the agent starts, then replace it with the actual response when it finishes:
app.event("app_mention", async ({ event, client }) => {
const threadTs = event.thread_ts ?? event.ts;
const userMessage = stripMentionText(event.text ?? "");
// Post thinking placeholder immediately
const thinkingMessage = await client.chat.postMessage({
token: process.env.SLACK_BOT_TOKEN,
channel: event.channel,
thread_ts: threadTs,
text: "_Thinking..._",
});
try {
const { response } = await agentChat(userMessage);
// Replace placeholder with actual response
await client.chat.update({
token: process.env.SLACK_BOT_TOKEN,
channel: event.channel,
ts: thinkingMessage.ts!,
text: markdownToSlack(response),
});
} catch (error) {
await client.chat.update({
token: process.env.SLACK_BOT_TOKEN,
channel: event.channel,
ts: thinkingMessage.ts!,
text: "Something went wrong.",
});
}
});
The user sees Thinking... in italics immediately, then it updates to the actual response. No silent wait.
Apply the same pattern to the DM handler — the structure is identical, just triggered by the message event.
The result
After these three changes, the Slack experience looks and feels like a proper integration:
- Messages arrive clean, without tag artifacts
- Responses format correctly with bold, italic, and code blocks
- Users see immediate feedback while the agent works
These details matter for anything you deploy to a client team or use daily yourself. The agent's intelligence does not change — but the experience around it changes significantly.
---
Author: FractionalSkill