From 866f9ddc42fa916fd1accd0c83d141afc40ac128 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Fri, 24 Jul 2026 21:08:14 +0200 Subject: [PATCH] ref(seer): Remove legacy PR-created attribution/linking from process_autofix_updates The SEER_PR_CREATED branch of process_autofix_updates records PR attribution and the run<->PR link only when a run has a group_id, so PRs from group-less runs were never attributed or linked. The new notify_seer_pr_created RPC now records both for every PR Seer creates, run-anchored and issue-optional, which makes this write redundant. Drop the attribution/link calls from the completion-hook path (and their now unused imports). Everything else stays: the Activity creation and the entrypoint fan-out for SEER_PR_CREATED are untouched, as is the GitHub-webhook backstop. The obsolete process_autofix_updates attribution/link tests move to the notify_seer_pr_created suite; the Activity test is kept. This is safe only once the notify_seer_pr_created receiver is deployed and Seer's writer calls it, so this PR must stay draft until then. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/sentry/seer/entrypoints/operator.py | 31 ----- .../sentry/seer/entrypoints/test_operator.py | 121 ------------------ 2 files changed, 152 deletions(-) diff --git a/src/sentry/seer/entrypoints/operator.py b/src/sentry/seer/entrypoints/operator.py index 49bdaf19253e..63eebe23227b 100644 --- a/src/sentry/seer/entrypoints/operator.py +++ b/src/sentry/seer/entrypoints/operator.py @@ -9,7 +9,6 @@ from sentry.models.group import Group from sentry.models.organization import Organization from sentry.organizations.services.organization import RpcOrganization -from sentry.pr_metrics.attribution import attribute_seer_created_pull_requests from sentry.seer.agent.client import SeerAgentClient from sentry.seer.agent.client_models import CodingAgentState, SeerRunState from sentry.seer.agent.client_utils import fetch_run_status @@ -31,7 +30,6 @@ SeerEntrypointKey, ) from sentry.seer.models import SeerPermissionError -from sentry.seer.pull_requests import link_seer_run_pull_requests from sentry.seer.seer_setup import has_seer_access from sentry.sentry_apps.event_types import SentryAppEventType from sentry.tasks.base import instrumented_task @@ -677,35 +675,6 @@ def process_autofix_updates( }, ) - if event_type == SentryAppEventType.SEER_PR_CREATED: - pull_requests = event_payload.get("pull_requests", []) - - if features.has("organizations:pr-metrics-attribution", organization): - try: - attribute_seer_created_pull_requests( - organization=organization, - pull_requests=pull_requests, - run_id=run_id, - group_id=group_id, - ) - except Exception: - logger.exception( - "seer.pr_attribution.failed", - extra={"group_id": group_id, "run_id": run_id}, - ) - - try: - link_seer_run_pull_requests( - organization=organization, - seer_run_state_id=run_id, - pull_requests=pull_requests, - ) - except Exception: - logger.exception( - "seer.pr_link.failed", - extra={"group_id": group_id, "run_id": run_id}, - ) - for entrypoint_key, entrypoint_cls in autofix_entrypoint_registry.registrations.items(): logging_ctx = { "organization_id": organization.id, diff --git a/tests/sentry/seer/entrypoints/test_operator.py b/tests/sentry/seer/entrypoints/test_operator.py index 9157e9b301f0..5bf17375ca0b 100644 --- a/tests/sentry/seer/entrypoints/test_operator.py +++ b/tests/sentry/seer/entrypoints/test_operator.py @@ -6,11 +6,6 @@ from fixtures.seer.webhooks import MOCK_RUN_ID from sentry.models.activity import Activity from sentry.models.organization import Organization -from sentry.models.pullrequest import ( - PullRequest, - PullRequestAttribution, - PullRequestAttributionSignalType, -) from sentry.organizations.services.organization.model import RpcOrganization from sentry.seer.agent.client_models import ( CodingAgentState, @@ -39,7 +34,6 @@ SeerEntrypointKey, SeerOperatorCacheResult, ) -from sentry.seer.models.run import SeerRunPullRequest, SeerRunType from sentry.sentry_apps.event_types import SentryAppEventType from sentry.testutils.asserts import assert_failure_metric from sentry.testutils.cases import TestCase @@ -350,121 +344,6 @@ def test_process_autofix_updates(self, mock_autofix_cache_get, _mock_has_access) cache_payload=cache_payload, ) - def _pr_created_event_payload(self) -> dict: - return { - "run_id": MOCK_RUN_ID, - "group_id": self.group.id, - "pull_requests": [ - { - "provider": "unknown", - "repo_name": "getsentry/sentry", - "pull_request": {"pr_id": 1, "pr_number": 99, "pr_url": "https://x/99"}, - } - ], - } - - @patch.object(SeerAutofixOperator, "has_access", return_value=True) - def test_process_autofix_updates_records_pr_attribution(self, _mock_has_access): - repo = self.create_repo(self.project, name="getsentry/sentry") - - with ( - self.feature("organizations:pr-metrics-attribution"), - override_options({"issues.record-seer-actions-as-activities": False}), - patch.dict( - "sentry.seer.entrypoints.operator.autofix_entrypoint_registry.registrations", - {}, - clear=True, - ), - ): - process_autofix_updates( - event_type=SentryAppEventType.SEER_PR_CREATED, - event_payload=self._pr_created_event_payload(), - organization_id=self.organization.id, - ) - - pull_request = PullRequest.objects.get(repository_id=repo.id, key="99") - attribution = PullRequestAttribution.objects.get(pull_request=pull_request) - assert attribution.signal_type == PullRequestAttributionSignalType.SENTRY_APP - assert attribution.signal_details is not None - assert attribution.signal_details["run_id"] == MOCK_RUN_ID - - @patch.object(SeerAutofixOperator, "has_access", return_value=True) - def test_process_autofix_updates_pr_attribution_disabled(self, _mock_has_access): - repo = self.create_repo(self.project, name="getsentry/sentry") - - # Feature flag off (default) — the attribution block must not run. - with ( - override_options({"issues.record-seer-actions-as-activities": False}), - patch.dict( - "sentry.seer.entrypoints.operator.autofix_entrypoint_registry.registrations", - {}, - clear=True, - ), - ): - process_autofix_updates( - event_type=SentryAppEventType.SEER_PR_CREATED, - event_payload=self._pr_created_event_payload(), - organization_id=self.organization.id, - ) - - assert not PullRequest.objects.filter(repository_id=repo.id).exists() - assert not PullRequestAttribution.objects.exists() - - @patch.object(SeerAutofixOperator, "has_access", return_value=True) - def test_process_autofix_updates_links_pull_requests(self, _mock_has_access): - repo = self.create_repo(self.project, name="getsentry/sentry") - seer_run = self.create_seer_run( - self.organization, type=SeerRunType.FEATURE_RUN, seer_run_state_id=MOCK_RUN_ID - ) - - with ( - override_options({"issues.record-seer-actions-as-activities": False}), - patch.dict( - "sentry.seer.entrypoints.operator.autofix_entrypoint_registry.registrations", - {}, - clear=True, - ), - ): - process_autofix_updates( - event_type=SentryAppEventType.SEER_PR_CREATED, - event_payload=self._pr_created_event_payload(), - organization_id=self.organization.id, - ) - - pull_request = PullRequest.objects.get(repository_id=repo.id, key="99") - link = SeerRunPullRequest.objects.get(pull_request=pull_request) - assert link.seer_run_id == seer_run.id - assert not PullRequestAttribution.objects.exists() - - @patch.object(SeerAutofixOperator, "has_access", return_value=True) - def test_process_autofix_updates_link_killswitch(self, _mock_has_access): - repo = self.create_repo(self.project, name="getsentry/sentry") - self.create_seer_run( - self.organization, type=SeerRunType.FEATURE_RUN, seer_run_state_id=MOCK_RUN_ID - ) - - with ( - override_options( - { - "issues.record-seer-actions-as-activities": False, - "seer.pull-request-linking.killswitch.enabled": True, - } - ), - patch.dict( - "sentry.seer.entrypoints.operator.autofix_entrypoint_registry.registrations", - {}, - clear=True, - ), - ): - process_autofix_updates( - event_type=SentryAppEventType.SEER_PR_CREATED, - event_payload=self._pr_created_event_payload(), - organization_id=self.organization.id, - ) - - assert not SeerRunPullRequest.objects.exists() - assert not PullRequest.objects.filter(repository_id=repo.id).exists() - def test_process_autofix_updates_no_operator_access(self) -> None: mock_entrypoint_cls = Mock(spec=SeerAutofixEntrypoint) event_type = SentryAppEventType.SEER_ROOT_CAUSE_COMPLETED