diff --git a/src/slack/send.blocks.test.ts b/src/slack/send.blocks.test.ts index 2b70b6c29b2..690f95120f0 100644 --- a/src/slack/send.blocks.test.ts +++ b/src/slack/send.blocks.test.ts @@ -4,6 +4,52 @@ import { createSlackSendTestClient, installSlackBlockTestMocks } from "./blocks. installSlackBlockTestMocks(); const { sendMessageSlack } = await import("./send.js"); +describe("sendMessageSlack NO_REPLY guard", () => { + it("suppresses NO_REPLY text before any Slack API call", async () => { + const client = createSlackSendTestClient(); + const result = await sendMessageSlack("channel:C123", "NO_REPLY", { + token: "xoxb-test", + client, + }); + + expect(client.chat.postMessage).not.toHaveBeenCalled(); + expect(result.messageId).toBe("suppressed"); + }); + + it("suppresses NO_REPLY with surrounding whitespace", async () => { + const client = createSlackSendTestClient(); + const result = await sendMessageSlack("channel:C123", " NO_REPLY ", { + token: "xoxb-test", + client, + }); + + expect(client.chat.postMessage).not.toHaveBeenCalled(); + expect(result.messageId).toBe("suppressed"); + }); + + it("does not suppress substantive text containing NO_REPLY", async () => { + const client = createSlackSendTestClient(); + await sendMessageSlack("channel:C123", "This is not a NO_REPLY situation", { + token: "xoxb-test", + client, + }); + + expect(client.chat.postMessage).toHaveBeenCalled(); + }); + + it("does not suppress NO_REPLY when blocks are attached", async () => { + const client = createSlackSendTestClient(); + const result = await sendMessageSlack("channel:C123", "NO_REPLY", { + token: "xoxb-test", + client, + blocks: [{ type: "section", text: { type: "mrkdwn", text: "content" } }], + }); + + expect(client.chat.postMessage).toHaveBeenCalled(); + expect(result.messageId).toBe("171234.567"); + }); +}); + describe("sendMessageSlack blocks", () => { it("posts blocks with fallback text when message is empty", async () => { const client = createSlackSendTestClient(); diff --git a/src/slack/send.ts b/src/slack/send.ts index 5905473970f..ede97bafd71 100644 --- a/src/slack/send.ts +++ b/src/slack/send.ts @@ -9,6 +9,7 @@ import { resolveChunkMode, resolveTextChunkLimit, } from "../auto-reply/chunk.js"; +import { isSilentReplyText } from "../auto-reply/tokens.js"; import { loadConfig } from "../config/config.js"; import { resolveMarkdownTableMode } from "../config/markdown-tables.js"; import { logVerbose } from "../globals.js"; @@ -231,6 +232,10 @@ export async function sendMessageSlack( opts: SlackSendOpts = {}, ): Promise { const trimmedMessage = message?.trim() ?? ""; + if (isSilentReplyText(trimmedMessage) && !opts.mediaUrl && !opts.blocks) { + logVerbose("slack send: suppressed NO_REPLY token before API call"); + return { messageId: "suppressed", channelId: "" }; + } const blocks = opts.blocks == null ? undefined : validateSlackBlocksArray(opts.blocks); if (!trimmedMessage && !opts.mediaUrl && !blocks) { throw new Error("Slack send requires text, blocks, or media");