← Back to PRs

#16570: feat(mattermost): add replyToMode threading support

by FBartos open 2026-02-14 21:47 View on GitHub →
channel: mattermost stale size: XS
## 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