#19274: feat(mattermost): enable threaded replies in channels
channel: mattermost
size: XS
Cluster:
Mattermost Threading Enhancements
## Summary
- Problem: Bot replies in Mattermost channels post as new top-level messages instead of threaded replies
- Why it matters: Channels become noisy and hard to follow with multiple conversations
- What changed: Added shared thread-state module so the send function knows which message to thread under, even when the outbound framework bypasses the deliver callback's replyToId
- What did NOT change: DM behavior, block streaming, existing replyToId passthrough — all preserved
## Change Type (select all)
- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Docs
- [ ] Security hardening
- [ ] Chore/infra
## Scope (select all touched areas)
- [ ] Gateway / orchestration
- [ ] Skills / tool execution
- [ ] Auth / tokens
- [ ] Memory / storage
- [x] Integrations
- [ ] API / contracts
- [ ] UI / DX
- [ ] CI/CD / infra
## Linked Issue/PR
- Closes #
- Related #14427
## User-visible / Behavior Changes
Bot replies in Mattermost channels now appear as threaded replies under the triggering message instead of new top-level posts. Works with block streaming enabled and disabled. No config changes required — threading is automatic for channel messages. DM behavior is unchanged.
## Security Impact (required)
- New permissions/capabilities? No
- Secrets/tokens handling changed? No
- New/changed network calls? No
- Command/tool execution surface changed? No
- Data access scope changed? No
## Repro + Verification
### Environment
- OS: macOS (Apple Silicon)
- Runtime/container: Node 25.5.0
- Model/provider: Anthropic Claude Opus 4.5
- Integration/channel: Mattermost Team Edition v10.11 ESR
- Relevant config: `chatmode: "onmessage"`, `blockStreaming: true`
### Steps
1. Send a message in a Mattermost channel where the bot is a member
2. Bot processes and replies
### Expected
- Bot reply appears as a threaded reply under the triggering message
### Actual (before fix)
- Bot reply appears as a new top-level message in the channel
## Evidence
- [x] Trace/log snippets
Logs confirm thread session creation but `replyToId=undefined` in send function before fix. After fix, `effectiveReplyToId` correctly resolves from shared thread state. Verified via `console.log` debugging in both monitor.ts and send.ts.
## Human Verification (required)
- Verified scenarios: Channel messages get threaded replies, DMs still work normally, block streaming enabled and disabled both work, replying within existing threads preserves the original thread root
- Edge cases checked: Multiple rapid messages in same channel, messages in different channels, DMs vs channel messages
- What you did **not** verify: Multi-account setups, onchar chatmode interaction
## Compatibility / Migration
- Backward compatible? Yes
- Config/env changes? No
- Migration needed? No
## Failure Recovery (if this breaks)
- How to disable/revert this change quickly: Delete `thread-state.ts`, revert the two changed lines in `monitor.ts` and `send.ts`
- Files/config to restore: `extensions/mattermost/src/thread-state.ts` (delete), `extensions/mattermost/src/mattermost/monitor.ts` (revert line 506), `extensions/mattermost/src/mattermost/send.ts` (revert lines 2, 215, 219)
- Known bad symptoms reviewers should watch for: Bot replies appearing as top-level messages (revert didn't take), or duplicate threaded replies
## Risks and Mitigations
- Risk: Shared in-memory Map could serve stale thread root if a channel receives rapid messages from different contexts
- Mitigation: Each new inbound message overwrites the stored root for that channel, and the Map is keyed per channel ID so cross-channel contamination is not possible
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR adds threaded reply support for Mattermost channel messages by introducing a shared thread-state module that tracks the most recent message ID per channel. When bot replies are sent via the outbound framework (which bypasses the deliver callback's `replyToId` parameter), the send function now falls back to the stored thread root, ensuring replies appear as threaded responses instead of top-level messages.
**Key changes:**
- New `thread-state.ts` module with a Map tracking `channelId -> threadRootId`
- `monitor.ts` sets the thread root for each incoming channel message (using `post.root_id` for existing threads, or `post.id` for new threads)
- `send.ts` uses `opts.replyToId ?? getThreadRoot(channelId)` to resolve the effective reply target
- DM messages are correctly excluded (no thread root is set when `kind === "direct"`)
**Implementation notes:**
- The deliver callback in `monitor.ts:763-777` already passes explicit `replyToId: threadRootId`, so normal bot replies were already threaded correctly
- The shared state primarily benefits CLI sends and other paths that don't have thread context
- The Map grows unbounded but this is acceptable for typical channel counts
- Race condition with concurrent messages in different threads is acknowledged in PR description; explicit `replyToId` takes precedence when provided
<h3>Confidence Score: 4/5</h3>
- This PR is safe to merge with low risk - it's a focused enhancement that preserves existing behavior
- The implementation is clean and well-scoped. Thread state is correctly set only for channel messages (excluding DMs), explicit `replyToId` takes precedence over fallback state, and the deliver callback already had correct threading. The main enhancement is for CLI/outbound sends. Minor point deduction for unbounded Map growth, though this is acceptable in practice.
- No files require special attention - all changes are straightforward
<sub>Last reviewed commit: 2079f07</sub>
<!-- greptile_other_comments_section -->
<sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#16570: feat(mattermost): add replyToMode threading support
by FBartos · 2026-02-14
91.0%
#2300: fix(mattermost): ensure replies create threads in channels
by blizzy78 · 2026-01-26
87.8%
#19213: Telegram: preserve DM topic thread in direct replies
by Kemalau · 2026-02-17
83.9%
#19403: feat(slack): add dm.threadSession option for per-message thread ses...
by Vasiliy-Bondarenko · 2026-02-17
82.2%
#19419: feat(slack): track thread participation for implicit mentions
by Utkarshbhimte · 2026-02-17
80.1%
#20406: fix(slack): respect replyToMode when computing statusThreadTs in DMs
by QuinnYates · 2026-02-18
80.0%
#23320: fix(slack): respect replyToMode when incomingThreadTs is auto-created
by dorukardahan · 2026-02-22
79.8%
#3368: fix: sessions navigation, DM thread display, and DM thread delivery...
by Lukavyi · 2026-01-28
79.5%
#16223: feat(slack): sticky thread routing — bypass @mention in active threads
by iamfuntime · 2026-02-14
79.1%
#19083: Slack: preserve per-thread context and consistent thread replies
by jkimbo · 2026-02-17
78.7%