From 19471ba1d1b749c19f33fe3d02ff5a68ea7e78e0 Mon Sep 17 00:00:00 2001 From: Rohit Malhotra Date: Mon, 15 Jun 2026 18:04:20 -0400 Subject: [PATCH] fix: Ignore OpenHands bot GitHub resolver events (#14832) Co-authored-by: openhands --- .../integrations/github/github_manager.py | 24 ++++++++ .../github/test_github_manager.py | 58 +++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/enterprise/integrations/github/github_manager.py b/enterprise/integrations/github/github_manager.py index c2bc530785..283fff8211 100644 --- a/enterprise/integrations/github/github_manager.py +++ b/enterprise/integrations/github/github_manager.py @@ -42,6 +42,12 @@ from openhands.app_server.types import ( ) from openhands.app_server.utils.logger import openhands_logger as logger +IGNORED_GITHUB_EVENT_SENDERS = frozenset( + { + 'openhands-ai[bot]', + } +) + class GithubManager(Manager[GithubViewType]): def __init__( @@ -123,6 +129,13 @@ class GithubManager(Manager[GithubViewType]): return False + def _get_ignored_sender_login(self, message: Message) -> str | None: + payload = message.message.get('payload', {}) + login = payload.get('sender', {}).get('login') + if login and login.lower() in IGNORED_GITHUB_EVENT_SENDERS: + return login + return None + def _get_issue_number_from_payload(self, message: Message) -> int | None: """Extract issue/PR number from a GitHub webhook payload. @@ -196,6 +209,11 @@ class GithubManager(Manager[GithubViewType]): async def is_job_requested(self, message: Message) -> bool: self._confirm_incoming_source_type(message) + ignored_sender = self._get_ignored_sender_login(message) + if ignored_sender: + logger.info('[GitHub] Ignoring event from %s', ignored_sender) + return False + installation_id = message.message['installation'] payload = message.message.get('payload', {}) repo_obj = payload.get('repository') @@ -236,6 +254,12 @@ class GithubManager(Manager[GithubViewType]): async def receive_message(self, message: Message): self._confirm_incoming_source_type(message) + + ignored_sender = self._get_ignored_sender_login(message) + if ignored_sender: + logger.info('[GitHub] Ignoring event from %s', ignored_sender) + return + try: await self.data_collector.process_payload(message) except Exception: diff --git a/enterprise/tests/unit/integrations/github/test_github_manager.py b/enterprise/tests/unit/integrations/github/test_github_manager.py index 18276bdaec..3db70e3dcd 100644 --- a/enterprise/tests/unit/integrations/github/test_github_manager.py +++ b/enterprise/tests/unit/integrations/github/test_github_manager.py @@ -7,6 +7,7 @@ Covers: - All supported trigger types: labeled issues, issue comments, PR comments, inline PR comments """ +from copy import deepcopy from unittest.mock import AsyncMock, MagicMock, patch import pytest @@ -316,6 +317,63 @@ class TestGithubManagerUserNotFound: assert "haven't created an OpenHands account" in comment_text assert 'sign up' in comment_text.lower() + @pytest.mark.asyncio + @patch('integrations.github.github_manager.Auth') + @patch('integrations.github.github_manager.GithubIntegration') + async def test_is_job_requested_ignores_openhands_bot_sender( + self, + mock_github_integration, + mock_auth, + mock_token_manager, + mock_data_collector, + github_issue_message, + ): + """Test that bot-authored comments from the OpenHands GitHub App do not start jobs.""" + github_issue_message = deepcopy(github_issue_message) + github_issue_message.message['payload']['sender'] = { + 'id': 188912522, + 'login': 'openhands-ai[bot]', + } + github_issue_message.message['payload']['comment']['body'] = ( + 'Documented upstream changes in @openhands/extensions.' + ) + + manager = GithubManager(mock_token_manager, mock_data_collector) + manager._user_has_write_access_to_repo = MagicMock(return_value=True) + + assert await manager.is_job_requested(github_issue_message) is False + manager._user_has_write_access_to_repo.assert_not_called() + + @pytest.mark.asyncio + @patch('integrations.github.github_manager.Auth') + @patch('integrations.github.github_manager.GithubIntegration') + async def test_receive_message_ignores_openhands_bot_sender_before_processing( + self, + mock_github_integration, + mock_auth, + mock_token_manager, + mock_data_collector, + github_issue_message, + ): + """Test that ignored bot events are dropped before collection or account lookup.""" + github_issue_message = deepcopy(github_issue_message) + github_issue_message.message['payload']['sender'] = { + 'id': 188912522, + 'login': 'openhands-ai[bot]', + } + github_issue_message.message['payload']['comment']['body'] = ( + 'Documented upstream changes in @openhands/extensions.' + ) + + manager = GithubManager(mock_token_manager, mock_data_collector) + manager.is_job_requested = AsyncMock(return_value=True) + + await manager.receive_message(github_issue_message) + + mock_data_collector.process_payload.assert_not_awaited() + manager.is_job_requested.assert_not_awaited() + mock_token_manager.get_user_id_from_idp_user_id.assert_not_called() + @patch('integrations.github.github_manager.Auth') @patch('integrations.github.github_manager.GithubIntegration') @patch('integrations.github.github_manager.logger')