diff --git a/agent/webapp.py b/agent/webapp.py index 74646cef..00acc727 100644 --- a/agent/webapp.py +++ b/agent/webapp.py @@ -1192,6 +1192,11 @@ _SUPPORTED_GH_EVENTS = frozenset( ) _SUPPORTED_GH_ISSUE_ACTIONS = frozenset(["edited", "opened", "reopened"]) _SUPPORTED_GH_PULL_REQUEST_ACTIONS = frozenset(["review_requested"]) +_SUPPORTED_GH_COMMENT_ACTIONS = { + "issue_comment": frozenset(["created", "edited"]), + "pull_request_review_comment": frozenset(["created", "edited"]), + "pull_request_review": frozenset(["submitted", "edited"]), +} def _build_github_issue_comments_text(comments: list[dict[str, Any]]) -> str: @@ -1817,10 +1822,23 @@ async def github_webhook(request: Request, background_tasks: BackgroundTasks) -> background_tasks.add_task(process_github_issue, payload, event_type) return {"status": "accepted", "message": "Processing GitHub issue event"} + action = payload.get("action", "") + supported_comment_actions = _SUPPORTED_GH_COMMENT_ACTIONS.get(event_type) + if supported_comment_actions is None: + logger.info("Ignoring unsupported GitHub payload shape for event=%s", event_type) + return {"status": "ignored", "reason": f"Unsupported payload for event type: {event_type}"} + if action and action not in supported_comment_actions: + logger.debug("Ignoring unsupported GitHub %s action: %s", event_type, action) + return {"status": "ignored", "reason": f"Unsupported GitHub {event_type} action: {action}"} + comment = payload.get("comment") or payload.get("review", {}) comment_body = (comment.get("body") or "") if comment else "" if not any(tag in comment_body.lower() for tag in OPEN_SWE_TAGS): - logger.info("Ignoring comment that does not mention @openswe or @open-swe") + logger.debug( + "Ignoring GitHub %s%s that does not mention @openswe or @open-swe", + event_type, + f" action={action}" if action else "", + ) return {"status": "ignored", "reason": "Comment does not mention @openswe or @open-swe"} logger.info("Accepted GitHub webhook: event=%s, scheduling background task", event_type) diff --git a/tests/test_github_issue_webhook.py b/tests/test_github_issue_webhook.py index 877d7667..07718061 100644 --- a/tests/test_github_issue_webhook.py +++ b/tests/test_github_issue_webhook.py @@ -5,6 +5,7 @@ import hashlib import hmac import importlib import json +import logging from fastapi.testclient import TestClient @@ -206,6 +207,7 @@ def test_github_webhook_accepts_issue_comment_events(monkeypatch) -> None: client, "issue_comment", { + "action": "created", "issue": {"id": 12345, "number": 42, "title": "Fix the flaky test"}, "comment": {"body": "@openswe please handle this"}, "repository": {"owner": {"login": "langchain-ai"}, "name": "open-swe"}, @@ -218,6 +220,72 @@ def test_github_webhook_accepts_issue_comment_events(monkeypatch) -> None: assert called["event_type"] == "issue_comment" +def test_github_webhook_ignores_unmentioned_comment_without_info_log(monkeypatch, caplog) -> None: + async def fake_process_github_pr_comment(payload: dict[str, object], event_type: str) -> None: + raise AssertionError("process_github_pr_comment should not be called") + + monkeypatch.setattr(webapp, "process_github_pr_comment", fake_process_github_pr_comment) + monkeypatch.setattr(webapp, "GITHUB_WEBHOOK_SECRET", _TEST_WEBHOOK_SECRET) + caplog.set_level(logging.INFO, logger=webapp.logger.name) + + client = TestClient(webapp.app) + response = _post_github_webhook( + client, + "pull_request_review_comment", + { + "action": "created", + "pull_request": { + "number": 1244, + "html_url": "https://github.com/langchain-ai/open-swe/pull/1244", + "base": {"sha": "base-sha"}, + "head": {"sha": "head-sha", "ref": "feature-branch"}, + }, + "comment": {"body": "Looks good to me"}, + "repository": {"owner": {"login": "langchain-ai"}, "name": "open-swe"}, + "sender": {"login": "octocat"}, + }, + ) + + assert response.status_code == 200 + assert response.json() == { + "status": "ignored", + "reason": "Comment does not mention @openswe or @open-swe", + } + assert "does not mention @openswe or @open-swe" not in caplog.text + + +def test_github_webhook_ignores_unsupported_comment_action(monkeypatch) -> None: + async def fake_process_github_pr_comment(payload: dict[str, object], event_type: str) -> None: + raise AssertionError("process_github_pr_comment should not be called") + + monkeypatch.setattr(webapp, "process_github_pr_comment", fake_process_github_pr_comment) + monkeypatch.setattr(webapp, "GITHUB_WEBHOOK_SECRET", _TEST_WEBHOOK_SECRET) + + client = TestClient(webapp.app) + response = _post_github_webhook( + client, + "pull_request_review", + { + "action": "dismissed", + "review": {"body": "@openswe please check this"}, + "pull_request": { + "number": 1244, + "html_url": "https://github.com/langchain-ai/open-swe/pull/1244", + "base": {"sha": "base-sha"}, + "head": {"sha": "head-sha", "ref": "feature-branch"}, + }, + "repository": {"owner": {"login": "langchain-ai"}, "name": "open-swe"}, + "sender": {"login": "octocat"}, + }, + ) + + assert response.status_code == 200 + assert response.json() == { + "status": "ignored", + "reason": "Unsupported GitHub pull_request_review action: dismissed", + } + + def test_github_webhook_blocks_reviewer_repo_not_in_reviewer_repo_allowlist(monkeypatch) -> None: called = False