Bug Description
When MATTERMOST_REPLY_MODE=thread and a user sends a message inside an existing CRT Thread, ALL Hermes replies (tool progress messages, final response) fail with HTTP 400:
{"id":"api.post.create_post.root_id.app_error","message":"Invalid RootId parameter."}
The user sees tool progress in the main channel at first, then it stops. The Thread gets no reply at all. The agent actually completes processing, but every send attempt fails silently.
Root Cause
gateway/platforms/mattermost.py line 273-274:
if reply_to and self._reply_mode == "thread":
payload["root_id"] = reply_to
reply_to comes from _reply_anchor_for_event(event) → event.message_id, which is the user's immediate message ID, NOT the thread's root message ID.
In Mattermost CRT, when replying inside an existing Thread:
- The user's message already has a
root_id (the thread root)
- Setting
root_id to the user's own message ID is invalid because that message is itself a reply, not a root-level post
- Mattermost rejects this with 400 "Invalid RootId parameter"
Correct behavior: root_id must point to the thread's root message, which is available as metadata["thread_id"] (set by _thread_metadata_for_source()).
Why top-level channel messages work: When the user sends from the main channel (not in a Thread), their message IS a root-level post, so using reply_to as root_id is valid.
Steps to Reproduce
- Set
MATTERMOST_REPLY_MODE=thread
- In Mattermost, reply to any message to create a CRT Thread
- Inside that Thread, send a message to @Hermes
- Observe: no reply in the Thread, tool progress in main channel stops updating, gateway logs show repeated 400 errors
Proposed Fix
- # Thread support: reply_to is the root post ID.
+ # Thread support: use the thread's root_id from metadata when
+ # replying inside an existing CRT Thread. Mattermost requires
+ # root_id to point to the root-level post, not a nested reply.
+ # Fall back to reply_to for top-level channel messages (where
+ # the user's message itself is a valid thread root).
if reply_to and self._reply_mode == "thread":
- payload["root_id"] = reply_to
+ thread_root = (metadata or {}).get("thread_id")
+ payload["root_id"] = thread_root or reply_to
The _thread_metadata_for_source() already produces {"thread_id": <root_id>} and passes it through metadata to send(). The fix simply uses it.
Environment
- Hermes Agent version: current main
- Mattermost version: 11.7.0 (Team Edition, Docker)
MATTERMOST_REPLY_MODE=thread
- Reproduced in both Thread-initiated and Thread-continuation conversations
Bug Description
When
MATTERMOST_REPLY_MODE=threadand a user sends a message inside an existing CRT Thread, ALL Hermes replies (tool progress messages, final response) fail with HTTP 400:{"id":"api.post.create_post.root_id.app_error","message":"Invalid RootId parameter."}The user sees tool progress in the main channel at first, then it stops. The Thread gets no reply at all. The agent actually completes processing, but every send attempt fails silently.
Root Cause
gateway/platforms/mattermost.pyline 273-274:reply_tocomes from_reply_anchor_for_event(event)→event.message_id, which is the user's immediate message ID, NOT the thread's root message ID.In Mattermost CRT, when replying inside an existing Thread:
root_id(the thread root)root_idto the user's own message ID is invalid because that message is itself a reply, not a root-level postCorrect behavior:
root_idmust point to the thread's root message, which is available asmetadata["thread_id"](set by_thread_metadata_for_source()).Why top-level channel messages work: When the user sends from the main channel (not in a Thread), their message IS a root-level post, so using
reply_toasroot_idis valid.Steps to Reproduce
MATTERMOST_REPLY_MODE=threadProposed Fix
The
_thread_metadata_for_source()already produces{"thread_id": <root_id>}and passes it throughmetadatatosend(). The fix simply uses it.Environment
MATTERMOST_REPLY_MODE=thread