From 30774786f158b9f20080296c33285ec8e8d10ddb Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 30 Apr 2026 05:06:35 +0100 Subject: [PATCH] fix: cap slack block fallback text --- CHANGELOG.md | 1 + extensions/slack/src/send.blocks.test.ts | 31 ++++++++++++++++++++++++ extensions/slack/src/send.ts | 6 ++++- 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7dc40dd72b..b680dd8809b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ Docs: https://docs.openclaw.ai - Slack/commands: cap native command argument-menu fallback rows to Slack's message block limit, so large plugin choice lists no longer make Slack reject the generated menu. Thanks @slackapi. - Slack/commands: drop fallback command argument buttons whose encoded values exceed Slack's button-value limit, so one oversized plugin choice no longer makes Slack reject the whole menu. Thanks @slackapi. - Slack/messages: merge message-tool presentation and interactive blocks on Slack sends, so buttons and selects are no longer dropped when a structured message body is also present. Thanks @slackapi. +- Slack/messages: cap Block Kit fallback text to Slack's send limit while preserving the rendered blocks, so long context fallbacks no longer make rich Slack messages fail with `msg_too_long`. Thanks @slackapi. - Channels/WhatsApp: require Baileys outbound message ids before marking auto-replies delivered, so transcript text and ack reactions no longer make failed group replies look sent. Fixes #49225. Thanks @TinyTb. - CLI/update: scope packaged Node compile caches by OpenClaw version and install metadata, so global installs no longer reuse stale compiled chunks after package updates. Thanks @pashpashpash. - Channels/Voice call: keep pre-auth webhook in-flight limiting active when socket remote address metadata is missing, so slow-body requests from stripped-IP proxy paths still share the fallback bucket. (#74453) Thanks @davidangularme. diff --git a/extensions/slack/src/send.blocks.test.ts b/extensions/slack/src/send.blocks.test.ts index 391388e1396..8e061d719c8 100644 --- a/extensions/slack/src/send.blocks.test.ts +++ b/extensions/slack/src/send.blocks.test.ts @@ -4,6 +4,7 @@ import { createSlackSendTestClient, installSlackBlockTestMocks } from "./blocks. installSlackBlockTestMocks(); const { sendMessageSlack } = await import("./send.js"); const SLACK_TEST_CFG = { channels: { slack: { botToken: "xoxb-test" } } }; +const SLACK_TEXT_LIMIT = 8000; describe("sendMessageSlack NO_REPLY guard", () => { it("suppresses NO_REPLY text before any Slack API call", async () => { @@ -170,6 +171,36 @@ describe("sendMessageSlack blocks", () => { ); }); + it("caps long fallback text while preserving blocks", async () => { + const client = createSlackSendTestClient(); + const longContextText = "a".repeat(3000); + const blocks = [ + { + type: "context", + elements: [ + { type: "mrkdwn", text: longContextText }, + { type: "mrkdwn", text: longContextText }, + { type: "mrkdwn", text: longContextText }, + ], + }, + ]; + + await sendMessageSlack("channel:C123", "", { + token: "xoxb-test", + cfg: SLACK_TEST_CFG, + client, + blocks, + }); + + expect(client.chat.postMessage).toHaveBeenCalledWith( + expect.objectContaining({ + text: expect.stringMatching(/…$/), + blocks, + }), + ); + expect(client.chat.postMessage.mock.calls[0]?.[0].text).toHaveLength(SLACK_TEXT_LIMIT); + }); + it("rejects blocks combined with mediaUrl", async () => { const client = createSlackSendTestClient(); await expect( diff --git a/extensions/slack/src/send.ts b/extensions/slack/src/send.ts index 10b1a131af2..f790989449f 100644 --- a/extensions/slack/src/send.ts +++ b/extensions/slack/src/send.ts @@ -26,6 +26,7 @@ import { SLACK_TEXT_LIMIT } from "./limits.js"; import { loadOutboundMediaFromUrl } from "./runtime-api.js"; import { parseSlackTarget } from "./targets.js"; import { resolveSlackBotToken } from "./token.js"; +import { truncateSlackText } from "./truncate.js"; const SLACK_UPLOAD_SSRF_POLICY = { allowedHostnames: ["*.slack.com", "*.slack-edge.com", "*.slack-files.com"], allowRfc2544BenchmarkRange: true, @@ -411,7 +412,10 @@ async function sendMessageSlackQueued(params: { if (opts.mediaUrl) { throw new Error("Slack send does not support blocks with mediaUrl"); } - const fallbackText = trimmedMessage || buildSlackBlocksFallbackText(blocks); + const fallbackText = truncateSlackText( + trimmedMessage || buildSlackBlocksFallbackText(blocks), + SLACK_TEXT_LIMIT, + ); const response = await postSlackMessageBestEffort({ client, channelId,