Skip to content

Mattermost: block streaming + threading produces duplicate messages (one threaded, one top-level) #41219

@mathiasnagler

Description

@mathiasnagler

Bug description

When block streaming is enabled for Mattermost (the default — blockStreamingCoalesceDefaults: { minChars: 1500, idleMs: 1000 }), agent replies to top-level channel messages produce two separate messages: one in the channel (top-level) and one threaded on the original message. These are different Mattermost posts with different content.

Root cause

Introduced in 2026.3.8 by #27744 (fix(mattermost): pass payload.replyToId as root_id for threaded replies). That commit correctly made the deliver callback pass payload.replyToId through via resolveMattermostReplyRootId(), but it exposed a latent inconsistency in how the block streaming and final payload paths handle threading:

  1. Block streaming path (createBlockReplyDeliveryHandler) sends coalesced chunks with replyToId set because resolveReplyToMode falls back to "all" for Mattermost (no threading dock defined). The deliver callback then threads them via resolveMattermostReplyRootId.

  2. Final payload path (buildReplyPayloads) — shouldDropFinalPayloads is supposed to suppress final payloads when streaming succeeded, but when streaming partially works or aborts, the fallback deduplication uses createBlockReplyPayloadKey which includes replyToId in the key. If replyToId differs between the block-streamed payload and the final payload, deduplication fails and both get delivered — one threaded, one top-level.

Contributing factors

  • Missing threading dock: Mattermost's plugin dock has no threading section, so resolveReplyToMode falls through to the hardcoded default "all". Telegram and Discord both explicitly define theirs as "off".
  • replyToId in payload dedup key: createBlockReplyPayloadKey includes replyToId, treating identical content with different threading targets as separate payloads.
  • No replyToMode config field: Mattermost's config schema doesn't include replyToMode, so users can't control threading behavior.

Repro steps

  1. Configure a Mattermost channel with default settings (block streaming enabled)
  2. Post a message in a public channel mentioning the bot
  3. Observe: bot sends a threaded reply AND a separate top-level reply to the channel

Expected behavior

Bot should send a single reply (threaded on the original message when replyToMode is "all").

Affected version

2026.3.8+ (introduced by #27744)

Fix plan

  1. Add threading section to the Mattermost plugin dock with resolveReplyToMode reading from channels.mattermost.replyToMode config (default: "all")
  2. Add replyToMode to the Mattermost config schema
  3. Remove replyToId from createBlockReplyPayloadKey — threading is a delivery concern, not content identity; identical content shouldn't be sent twice because of different threading targets
  4. Add tests covering block streaming + threading dedup and resolveMattermostReplyRootId with block streaming payloads

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    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