Skip to content

Commit 7e42464

Browse files
authored
fix: slack section limiting (#4004)
Signed-off-by: Uroš Marolt <uros@marolt.me>
1 parent fde44d2 commit 7e42464

1 file changed

Lines changed: 40 additions & 19 deletions

File tree

services/libs/slack/src/notify.ts

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const log = getServiceLogger()
1010
// Slack message size limit (keeping it conservative at 30KB)
1111
const MAX_MESSAGE_SIZE = 30 * 1024 // 30KB in bytes
1212
const MAX_BLOCKS = 50
13+
const MAX_SECTION_TEXT = 2900 // Slack hard limit is 3000 chars per section block
1314

1415
interface SlackBlock {
1516
type: string
@@ -25,32 +26,52 @@ interface SlackBlock {
2526
}
2627

2728
/**
28-
* Build content blocks from either a simple string or an array of sections
29+
* Split text into section blocks, respecting Slack's 3000 char per-section limit.
30+
* Splits at line boundaries where possible; hard-truncates individual lines that
31+
* are themselves longer than the limit.
32+
*/
33+
function splitIntoSectionBlocks(text: string): SlackBlock[] {
34+
const blocks: SlackBlock[] = []
35+
const lines = text.split('\n')
36+
let current = ''
37+
38+
for (let line of lines) {
39+
// Hard-truncate a single line that exceeds the limit on its own
40+
if (line.length > MAX_SECTION_TEXT) {
41+
line = `${line.slice(0, MAX_SECTION_TEXT - 1)}…`
42+
}
43+
44+
const candidate = current ? `${current}\n${line}` : line
45+
if (candidate.length > MAX_SECTION_TEXT && current) {
46+
blocks.push({ type: 'section', text: { type: 'mrkdwn', text: current } })
47+
current = line
48+
} else {
49+
current = candidate
50+
}
51+
}
52+
53+
if (current) {
54+
blocks.push({ type: 'section', text: { type: 'mrkdwn', text: current } })
55+
}
56+
57+
return blocks
58+
}
59+
60+
/**
61+
* Build content blocks from either a simple string or an array of sections.
62+
* Splits text that exceeds Slack's 3000 char per-section limit at line boundaries.
2963
*/
3064
function buildContentBlocks(content: string | SlackMessageSection[]): SlackBlock[] {
3165
if (typeof content === 'string') {
32-
// Simple string content - single section
33-
return [
34-
{
35-
type: 'section',
36-
text: {
37-
type: 'mrkdwn',
38-
text: content,
39-
},
40-
},
41-
]
66+
return splitIntoSectionBlocks(content)
4267
}
4368

44-
// Multiple sections - create a block for each section
4569
const blocks: SlackBlock[] = []
4670
for (const section of content) {
47-
blocks.push({
48-
type: 'section',
49-
text: {
50-
type: 'mrkdwn',
51-
text: `*${section.title}*\n${section.text}`,
52-
},
53-
})
71+
const fullText = `*${section.title}*\n${section.text}`
72+
for (const block of splitIntoSectionBlocks(fullText)) {
73+
blocks.push(block)
74+
}
5475
}
5576
return blocks
5677
}

0 commit comments

Comments
 (0)