#16570: feat(mattermost): add replyToMode threading support
channel: mattermost
stale
size: XS
Cluster:
Mattermost Threading Enhancements
## Summary
- **Problem:** The Mattermost extension has no `replyToMode` config option, so the bot always replies as top-level messages when users send top-level messages. There is no way to force threaded replies.
- **Why it matters:** In busy channels, top-level bot replies create noise. Threaded replies keep conversations organized — Slack, Discord, Telegram, Google Chat, and Matrix extensions already support this.
- **What changed:** Added `replyToMode` (`"off"` | `"first"` | `"all"`) config field to the Mattermost extension and wired up the SDK's existing `threading.resolveReplyToMode` adapter. 4 files, +7 lines.
- **What did NOT change (scope boundary):** No changes to `monitor.ts`, `send.ts`, or `client.ts`. No changes to inbound message handling, outbound delivery, or any other extension. The SDK's shared reply infrastructure handles all threading logic once the adapter is wired up.
## 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 # N/A (no existing issue)
- Related # N/A
## User-visible / Behavior Changes
- New optional config field `channels.mattermost.replyToMode` accepting `"off"` (default), `"first"`, or `"all"`.
- When set to `"all"`, the bot replies in a thread under the original message instead of posting a new top-level message.
- When set to `"first"`, only the first reply is threaded.
- Default is `"off"` — no behavior change for existing users.
- Supported at both base level and per-account level (via `channels.mattermost.accounts.<id>.replyToMode`).
## Security Impact (required)
- New permissions/capabilities? `No`
- Secrets/tokens handling changed? `No`
- New/changed network calls? `No` — same Mattermost `posts` API, just populates the existing `root_id` field
- Command/tool execution surface changed? `No`
- Data access scope changed? `No`
## Repro + Verification
### Environment
- OS: Linux (remote server) / any
- Runtime/container: Node.js with OpenClaw gateway
- Model/provider: Any
- Integration/channel: Mattermost
- Relevant config (redacted):
```json
{
"channels": {
"mattermost": {
"botToken": "REDACTED",
"baseUrl": "https://mattermost.example.com",
"replyToMode": "all"
}
}
}
```
### Steps
1. Set `channels.mattermost.replyToMode` to `"all"` in `openclaw.json`
2. Restart the gateway
3. Send a top-level message in a Mattermost channel where the bot is active
### Expected
- Bot replies in a thread under the original message
### Actual
- Without this PR: bot replies as a new top-level message
- With this PR: bot replies in a thread (when `replyToMode: "all"`)
## Evidence
- [ ] Failing test/log before + passing after
- [ ] Trace/log snippets
- [ ] Screenshot/recording
- [ ] Perf numbers (if relevant)
N/A — code-level change follows the exact same pattern as Discord (`channel.ts:159`), Telegram (`channel.ts:182`), Google Chat (`channel.ts:75`), and Matrix (`channel.ts:186-188`). No new logic was introduced; only the existing SDK adapter was wired up.
## Human Verification (required)
- **Verified scenarios:** Code review confirming the pattern matches Discord/Telegram/Google Chat/Matrix implementations exactly. Config schema validates correctly with `.strict()` mode.
- **Edge cases checked:** Default value (`"off"`) preserves existing behavior. Per-account override works via `mergeMattermostAccountConfig` spread merge (same mechanism as all other account-level fields).
- **What you did not verify:** Runtime end-to-end test on a live Mattermost instance.
## Compatibility / Migration
- Backward compatible? `Yes` — default is `"off"`, identical to current behavior
- Config/env changes? `Yes` — new optional field `replyToMode` in Mattermost config
- Migration needed? `No`
## Failure Recovery (if this breaks)
- **How to disable/revert:** Remove `replyToMode` from config (or set to `"off"`), restart gateway. Behavior returns to current default.
- **Files/config to restore:** `openclaw.json` — remove the `replyToMode` key
- **Known bad symptoms:** Bot replies appearing in wrong threads, or `root_id` errors in Mattermost API responses
## Risks and Mitigations
- **Risk:** The SDK's `resolveReplyToMode` adapter sets `root_id` to the inbound post's ID for top-level messages. If Mattermost's API rejects a `root_id` pointing to a non-thread post, replies could fail silently.
- **Mitigation:** Mattermost's API treats any post ID as a valid `root_id` — setting it on a top-level post creates a new thread. This is the documented Mattermost threading behavior and is how all Mattermost clients create threads.
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Added `replyToMode` config field to the Mattermost extension, enabling threaded reply control with `"off"` (default), `"first"`, or `"all"` modes. The implementation wires up the SDK's existing threading adapter using the exact same pattern as Discord (channel.ts:159), Telegram (channel.ts:182), Google Chat (channel.ts:75), and Matrix (channel.ts:186-188).
- Config field added to schema with proper validation (`z.enum()`)
- Type definitions updated in both `MattermostAccountConfig` and `ResolvedMattermostAccount`
- Account resolution properly merges base + account-level config (same mechanism as all other fields)
- Threading adapter uses `resolveMattermostAccount()` to respect per-account overrides
- No changes to send logic required — existing `replyToId` parameter flows through to Mattermost API's `root_id` field
The change is minimal, follows established patterns, and maintains backward compatibility with the default `"off"` behavior.
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge with minimal risk
- The implementation is a straightforward feature addition that follows the exact pattern used by 4+ other extensions (Discord, Telegram, Google Chat, Matrix). The changes are isolated to config schema, types, account resolution, and the threading adapter—no modifications to core send/monitor logic. The default value preserves existing behavior, making this fully backward compatible. All changes maintain type safety with proper zod validation and TypeScript typing.
- No files require special attention
<sub>Last reviewed commit: 137cbef</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
#19274: feat(mattermost): enable threaded replies in channels
by rockinyp · 2026-02-17
91.0%
#2300: fix(mattermost): ensure replies create threads in channels
by blizzy78 · 2026-01-26
83.3%
#19403: feat(slack): add dm.threadSession option for per-message thread ses...
by Vasiliy-Bondarenko · 2026-02-17
80.8%
#23320: fix(slack): respect replyToMode when incomingThreadTs is auto-created
by dorukardahan · 2026-02-22
80.2%
#19213: Telegram: preserve DM topic thread in direct replies
by Kemalau · 2026-02-17
79.6%
#20928: mattermost: add readMessages action for channel history
by hubertusgbecker · 2026-02-19
79.6%
#22075: mattermost: honor account requireMention override
by armindocachada · 2026-02-20
79.1%
#20406: fix(slack): respect replyToMode when computing statusThreadTs in DMs
by QuinnYates · 2026-02-18
78.7%
#10587: fix(extensions/mattermost): pass requireMention override from chatm...
by baku4 · 2026-02-06
78.7%
#23226: fix(msteams): proactive messaging, EADDRINUSE fix, tool status, ada...
by TarogStar · 2026-02-22
78.3%