From 0ef04bc3404e9adf8d37d8d6dbaf6d654c93791d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 19 Feb 2026 18:14:04 +0000 Subject: [PATCH] feat(seer): Validate event_payload before scheduling code review tasks Adds validation to schedule_task() to catch schema mismatches early, before tasks are scheduled to the task broker. This prevents task failures when model changes are deployed to task brokers before Sentry workers are updated. Changes: - Validate transformed event payload using Pydantic models before scheduling the Celery task - Use appropriate model (SeerCodeReviewTaskRequestForPrClosed or SeerCodeReviewTaskRequestForPrReview) based on request_type - Log validation failures with detailed context - Record INVALID_PAYLOAD metric when validation fails - Add tests to verify validation happens before task scheduling Fixes: CW-837 Co-authored-by: Armen Zambrano G. --- src/sentry/seer/code_review/webhooks/task.py | 24 ++++++++ .../code_review/webhooks/test_pull_request.py | 58 +++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/src/sentry/seer/code_review/webhooks/task.py b/src/sentry/seer/code_review/webhooks/task.py index ad1c0a34411f07..4e010918eebb36 100644 --- a/src/sentry/seer/code_review/webhooks/task.py +++ b/src/sentry/seer/code_review/webhooks/task.py @@ -61,6 +61,30 @@ def schedule_task( ) return + # Validate payload before scheduling to catch schema mismatches early + try: + request_type = transformed_event.get("request_type") + if request_type == "pr-closed": + SeerCodeReviewTaskRequestForPrClosed.parse_obj(transformed_event) + else: + SeerCodeReviewTaskRequestForPrReview.parse_obj(transformed_event) + except Exception as e: + logger.warning( + "%s.validation_failed_before_scheduling", + PREFIX, + extra={ + "error": str(e), + "github_event": github_event.value, + "github_event_action": github_event_action, + "organization_id": organization.id, + "repo_id": repo.id, + }, + ) + record_webhook_filtered( + github_event, github_event_action, WebhookFilteredReason.INVALID_PAYLOAD + ) + return + # Convert enum to string for Celery serialization process_github_webhook_event.delay( github_event=github_event.value, diff --git a/tests/sentry/seer/code_review/webhooks/test_pull_request.py b/tests/sentry/seer/code_review/webhooks/test_pull_request.py index e5c3a8d9e31a8d..3219cb43bc1123 100644 --- a/tests/sentry/seer/code_review/webhooks/test_pull_request.py +++ b/tests/sentry/seer/code_review/webhooks/test_pull_request.py @@ -381,3 +381,61 @@ def test_pull_request_closed_draft_still_sends_to_seer(self) -> None: call_kwargs = self.mock_seer.call_args[1] payload = call_kwargs["payload"] assert payload["request_type"] == SeerCodeReviewRequestType.PR_CLOSED.value + + def test_validation_happens_before_task_scheduling_pr_closed(self) -> None: + """Test that invalid pr-closed payloads are caught before scheduling the Celery task.""" + with ( + self.code_review_setup(), + self.tasks(), + patch( + "sentry.seer.code_review.webhooks.task.transform_webhook_to_codegen_request" + ) as mock_transform, + ): + # Return an invalid payload missing required fields for pr-closed + mock_transform.return_value = { + "request_type": "pr-closed", + "data": { + # Missing required fields like repo, pr_id, etc. + "invalid": "payload" + }, + } + + event = orjson.loads(PULL_REQUEST_OPENED_EVENT_EXAMPLE) + event["action"] = "closed" + + self._send_webhook_event( + GithubWebhookType.PULL_REQUEST, + orjson.dumps(event), + ) + + # Task should NOT be scheduled due to validation failure + self.mock_seer.assert_not_called() + + def test_validation_happens_before_task_scheduling_pr_review(self) -> None: + """Test that invalid pr-review payloads are caught before scheduling the Celery task.""" + with ( + self.code_review_setup(), + self.tasks(), + patch( + "sentry.seer.code_review.webhooks.task.transform_webhook_to_codegen_request" + ) as mock_transform, + ): + # Return an invalid payload missing required fields for pr-review + mock_transform.return_value = { + "request_type": "pr-review", + "data": { + # Missing required fields + "invalid": "payload" + }, + } + + event = orjson.loads(PULL_REQUEST_OPENED_EVENT_EXAMPLE) + event["action"] = "opened" + + self._send_webhook_event( + GithubWebhookType.PULL_REQUEST, + orjson.dumps(event), + ) + + # Task should NOT be scheduled due to validation failure + self.mock_seer.assert_not_called()