Skip to content

Discord: padded table in code block wastes char budget, causes mid-table message split #1151

Description

@chaodu-agent

Description

When table_mode = "code", render_table_code pads every cell to the widest cell in that column. For tables with one very long cell (e.g. a Finding description of 80-100+ chars), every row gets padded to the same width regardless of its own content length.

On Discord (message_limit = 2000), this padding quickly exhausts the char budget, causing split_message to split the table mid-way between rows — resulting in two separate code blocks across two Discord messages.

Additionally, when a split occurs and the original message contains bot mentions (<@BotB>) or uses reply_to, only the first chunk carries the mention/reference. Under allow_bot_messages = "mentions", the receiving bot only processes the first chunk — the rest of the content is silently dropped.

Reproduction

A table like the PR review findings table:

  • 4 columns: #, Severity, Finding, Location
  • Finding column has one cell ~90 chars long
  • Each padded row ≈ 140-150 chars
  • Header + divider + 9 rows + fences + preamble text > 2000 chars
  • Result: table splits between rows 6 and 7; only first chunk delivered to mentioned bot

Root Cause

  • split_message has no table-awareness — it splits at line boundaries within code fences
  • Slack avoids this by using message_limit = 11,900 (Block Kit cap, PR fix(slack): dedupe native-stream finalize + render final as Block Kit markdown #1056), but Discord API hard-limits at 2000
  • The padding spaces are purely cosmetic but consume significant char budget
  • reply_to (message_reference) only applies to the first Discord message; subsequent chunks have no reply context
  • Bot message gating (allow_bot_messages) is per-message with no thread context awareness

Expected Behavior

When a bot reply is split into multiple Discord messages:

  1. All chunks should be receivable by the mentioned bot (not just the first)
  2. Tables should avoid unnecessary mid-table splits when possible
  3. per-thread batching on the receiver side should reassemble all chunks into one complete ACP turn

Solution: Mention Propagation on Split Chunks

After split_message, append all detected mentions to each subsequent chunk so every piece passes the receiving bot's gate.

Flow (current — broken)

┌─────────────────────────────────────────────────────────────────────┐
│                        BotA (sender)                                │
│                                                                     │
│  Agent reply: "...<@BotB>...findings table..." (5000 chars)         │
└─────────────────────────────┬───────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────────┐
│                    split_message(text, 2000)                         │
│                                                                     │
│  chunk 1: "...<@BotB>...rows 1-6..."  (≤2000)                      │
│  chunk 2: "...rows 7-9..."            (≤2000)  ← no mention!       │
│  chunk 3: "...summary..."             (≤2000)  ← no mention!       │
└─────────────────────────────┬───────────────────────────────────────┘
                              │
                              ▼  Discord sends 3 MESSAGE_CREATE events
┌─────────────────────────────────────────────────────────────────────┐
│                     BotB (receiver)                                  │
│                                                                     │
│  allow_bot_messages = "mentions" gate:                               │
│                                                                     │
│  chunk 1: has <@BotB> → PASS ✅                                     │
│  chunk 2: no <@BotB>  → REJECTED ❌ (return early)                  │
│  chunk 3: no <@BotB>  → REJECTED ❌ (return early)                  │
│                                                                     │
│  → Agent only sees chunk 1 (partial table)                          │
└─────────────────────────────────────────────────────────────────────┘

Flow (proposed fix)

┌─────────────────────────────────────────────────────────────────────┐
│                        BotA (sender)                                │
│                                                                     │
│  Agent reply: "...<@BotB>...findings table..." (5000 chars)         │
└─────────────────────────────┬───────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────────┐
│                    split_message(text, 2000)                         │
│                                                                     │
│  chunk 1: "...<@BotB>...rows 1-6..."  (≤2000)                      │
│  chunk 2: "...rows 7-9..."            (≤2000)                       │
│  chunk 3: "...summary..."             (≤2000)                       │
└─────────────────────────────┬───────────────────────────────────────┘
                              │
                              ▼  ★ Mention Propagation
┌─────────────────────────────────────────────────────────────────────┐
│              Detection phase:                                        │
│                                                                     │
│  1. Explicit mentions:  <@BotB>  <@&RoleID>                         │
│  2. reply_to directive: [[reply_to:msg_id]]                         │
│     → resolve msg_id → get original author UID                      │
│                                                                     │
│  Append phase (chunks 2+):                                          │
│                                                                     │
│  chunk 1: "...<@BotB>...rows 1-6..."           (already has it)     │
│  chunk 2: "...rows 7-9...\n<@BotB>"            ← appended           │
│  chunk 3: "...summary...\n<@BotB>"             ← appended           │
└─────────────────────────────┬───────────────────────────────────────┘
                              │
                              ▼  Discord sends 3 MESSAGE_CREATE events
┌─────────────────────────────────────────────────────────────────────┐
│                     BotB (receiver)                                  │
│                                                                     │
│  allow_bot_messages = "mentions" gate:                               │
│                                                                     │
│  chunk 1: has <@BotB> → PASS ✅                                     │
│  chunk 2: has <@BotB> → PASS ✅                                     │
│  chunk 3: has <@BotB> → PASS ✅                                     │
│                                                                     │
│  ┌─── message_processing_mode = "per-thread" ───┐                  │
│  │                                               │                  │
│  │  3 messages, same thread, same sender         │                  │
│  │  → batched into ONE ACP turn                  │                  │
│  │  → agent sees complete content                │                  │
│  └───────────────────────────────────────────────┘                  │
│                                                                     │
│  Agent processes full table ✅                                       │
└─────────────────────────────────────────────────────────────────────┘

Key Design Points

  1. Detect both mention sources: explicit <@UID> / <@&RoleID> AND reply_to targets (reply_to only applies to first message in Discord — subsequent chunks lose that context entirely)
  2. Reserve char budget: split_message limit should account for appended mentions (e.g. 2000 - len(mentions))
  3. Deduplicate: only append mentions not already present in that chunk
  4. Works with per-thread batching: all chunks pass the gate → dispatcher batches them into one ACP turn → agent sees full content

Secondary Improvement: Table Rendering

The padding waste is a separate (visual) issue. If a single rendered table exceeds message_limit, consider:

  • Column width cap (e.g. max 40 chars per column, truncate with )
  • Auto-fallback to bullets mode when rendered table would exceed limit
  • This reduces the frequency of splits but does not eliminate them — mention propagation is still needed as the safety net

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingneeds-triagep1High — address this sprint

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions