diff --git a/nanobot/channels/mattermost/runtime.py b/nanobot/channels/mattermost/runtime.py index dc58e7698..048d67158 100644 --- a/nanobot/channels/mattermost/runtime.py +++ b/nanobot/channels/mattermost/runtime.py @@ -221,6 +221,10 @@ class MattermostChannel(BaseChannel): self.logger.warning("failed to parse post json") return + post_type = post.get("type") + if isinstance(post_type, str) and post_type.startswith("system_"): + return + sender_id = post.get("user_id", "") channel_id = post.get("channel_id", "") message_text = post.get("message", "") diff --git a/nanobot/channels/mattermost/tests/test_mattermost_channel.py b/nanobot/channels/mattermost/tests/test_mattermost_channel.py index 1960e10b3..3ca99738d 100644 --- a/nanobot/channels/mattermost/tests/test_mattermost_channel.py +++ b/nanobot/channels/mattermost/tests/test_mattermost_channel.py @@ -463,6 +463,32 @@ async def test_posted_thread_event_uses_thread_policy(): assert mock_handle.call_args.kwargs["session_key"] == "mattermost:channel_1:root_1" +@pytest.mark.asyncio +@pytest.mark.parametrize("post_type", ["system_join_channel", "system_leave_channel"]) +async def test_posted_event_ignores_system_posts(post_type: str): + channel, _ = _make_channel({"groupPolicy": "open"}) + channel._self_id = "bot_id" + with patch.object(channel, "_handle_message", AsyncMock()) as mock_handle: + ws_msg = { + "event": "posted", + "data": { + "channel_type": "O", + "post": json.dumps({ + "id": "system_post_1", + "user_id": "user_1", + "channel_id": "channel_1", + "message": "A user joined or left the channel.", + "type": post_type, + }), + }, + "broadcast": {}, + } + + await channel._handle_ws_message(ws_msg) + + mock_handle.assert_not_awaited() + + @pytest.mark.asyncio async def test_group_policy_in_thread_allowlist(): """Thread uses allowlist policy when configured."""