-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
fix(coding integrations): add catching for integration not found error #111691
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
13244c8
07d8d17
72c5036
db34702
21286fc
5dcf9ea
4565b34
284eabb
1b40bf9
cc2fc50
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -8,14 +8,22 @@ | |||||||||
| from sentry import features | ||||||||||
| from sentry.models.group import Group | ||||||||||
| from sentry.models.organization import Organization | ||||||||||
| from sentry.models.project import Project | ||||||||||
| from sentry.seer.autofix.autofix_agent import ( | ||||||||||
| AutofixStep, | ||||||||||
| trigger_autofix_explorer, | ||||||||||
| trigger_coding_agent_handoff, | ||||||||||
| trigger_push_changes, | ||||||||||
| ) | ||||||||||
| from sentry.seer.autofix.coding_agent import IntegrationNotFound | ||||||||||
| from sentry.seer.autofix.constants import AutofixReferrer | ||||||||||
| from sentry.seer.autofix.utils import AutofixStoppingPoint, get_project_seer_preferences | ||||||||||
| from sentry.seer.autofix.utils import ( | ||||||||||
| AutofixStoppingPoint, | ||||||||||
| get_project_seer_preferences, | ||||||||||
| resolve_repository_ids, | ||||||||||
| set_project_seer_preference, | ||||||||||
| write_preference_to_sentry_db, | ||||||||||
| ) | ||||||||||
| from sentry.seer.entrypoints.operator import SeerAutofixOperator, process_autofix_updates | ||||||||||
| from sentry.seer.explorer.client_models import Artifact | ||||||||||
| from sentry.seer.explorer.client_utils import fetch_run_status | ||||||||||
|
|
@@ -25,6 +33,7 @@ | |||||||||
| SeerApiResponseValidationError, | ||||||||||
| SeerAutomationHandoffConfiguration, | ||||||||||
| ) | ||||||||||
| from sentry.seer.models.seer_api_models import SeerProjectPreference | ||||||||||
| from sentry.seer.supergroups.embeddings import trigger_supergroups_embedding | ||||||||||
| from sentry.sentry_apps.metrics import SentryAppEventType | ||||||||||
| from sentry.sentry_apps.tasks.sentry_apps import broadcast_webhooks_for_organization | ||||||||||
|
|
@@ -472,6 +481,35 @@ def _get_handoff_config_if_applicable( | |||||||||
|
|
||||||||||
| return handoff_config | ||||||||||
|
|
||||||||||
| @classmethod | ||||||||||
| def _clear_handoff_preference( | ||||||||||
| cls, project: Project, run_id: int, organization: Organization | ||||||||||
| ) -> None: | ||||||||||
| """Clear automation_handoff from project preferences after integration is not found.""" | ||||||||||
| try: | ||||||||||
| preference_response = get_project_seer_preferences(project.id) | ||||||||||
| if preference_response and preference_response.preference: | ||||||||||
| updated_preference = preference_response.preference.copy( | ||||||||||
| update={"automation_handoff": None} | ||||||||||
| ) | ||||||||||
| set_project_seer_preference(updated_preference) | ||||||||||
|
|
||||||||||
| if features.has("organizations:seer-project-settings-dual-write", organization): | ||||||||||
| try: | ||||||||||
| validated_pref = SeerProjectPreference.validate(updated_preference) | ||||||||||
| resolved_pref = resolve_repository_ids(organization.id, [validated_pref]) | ||||||||||
| write_preference_to_sentry_db(project, resolved_pref[0]) | ||||||||||
|
Comment on lines
+500
to
+501
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit
Suggested change
|
||||||||||
| except Exception: | ||||||||||
| logger.exception( | ||||||||||
| "seer.write_preferences.failed", | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to make this a bit more distinguishable from other logs:
Suggested change
|
||||||||||
| extra={"project_id": project.id, "organization_id": organization.id}, | ||||||||||
| ) | ||||||||||
| except (SeerApiError, SeerApiResponseValidationError): | ||||||||||
| logger.exception( | ||||||||||
| "autofix.on_completion_hook.clear_handoff_preference_failed", | ||||||||||
| extra={"run_id": run_id, "organization_id": organization.id}, | ||||||||||
| ) | ||||||||||
|
|
||||||||||
| @classmethod | ||||||||||
| def _trigger_coding_agent_handoff( | ||||||||||
| cls, | ||||||||||
|
|
@@ -508,6 +546,16 @@ def _trigger_coding_agent_handoff( | |||||||||
| "failures": len(result.get("failures", [])), | ||||||||||
| }, | ||||||||||
| ) | ||||||||||
| except IntegrationNotFound: | ||||||||||
| logger.exception( | ||||||||||
| "autofix.on_completion_hook.coding_agent_handoff_integration_not_found", | ||||||||||
| extra={ | ||||||||||
| "run_id": run_id, | ||||||||||
| "organization_id": organization.id, | ||||||||||
| "integration_id": handoff_config.integration_id, | ||||||||||
| }, | ||||||||||
| ) | ||||||||||
| cls._clear_handoff_preference(group.project, run_id, organization) | ||||||||||
| except Exception: | ||||||||||
| logger.exception( | ||||||||||
| "autofix.on_completion_hook.coding_agent_handoff_failed", | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,6 +36,7 @@ | |
| from sentry_protos.snuba.v1.trace_item_attribute_pb2 import AttributeKey, AttributeValue, StrArray | ||
| from sentry_protos.snuba.v1.trace_item_filter_pb2 import ComparisonFilter, TraceItemFilter | ||
|
|
||
| from sentry import features | ||
| from sentry.api.api_owners import ApiOwner | ||
| from sentry.api.api_publish_status import ApiPublishStatus | ||
| from sentry.api.authentication import AuthenticationSiloLimit, StandardAuthentication | ||
|
|
@@ -73,8 +74,19 @@ | |
| get_attribute_values_with_substring, | ||
| ) | ||
| from sentry.seer.autofix.autofix_tools import get_error_event_details, get_profile_details | ||
| from sentry.seer.autofix.coding_agent import launch_coding_agents_for_run | ||
| from sentry.seer.autofix.utils import AutofixTriggerSource | ||
| from sentry.seer.autofix.coding_agent import ( | ||
| AutofixStateNotFound, | ||
| IntegrationNotFound, | ||
| OrganizationNotFound, | ||
| StateReposNotFound, | ||
| launch_coding_agents_for_run, | ||
| ) | ||
| from sentry.seer.autofix.utils import ( | ||
| AutofixTriggerSource, | ||
| get_project_seer_preferences, | ||
| resolve_repository_ids, | ||
| write_preference_to_sentry_db, | ||
| ) | ||
| from sentry.seer.constants import SEER_SUPPORTED_SCM_PROVIDERS, SeerSCMProvider | ||
| from sentry.seer.entrypoints.operator import SeerAutofixOperator, process_autofix_updates | ||
| from sentry.seer.explorer.custom_tool_utils import call_custom_tool | ||
|
|
@@ -101,9 +113,10 @@ | |
| get_trace_item_attributes, | ||
| rpc_get_profile_flamegraph, | ||
| rpc_get_trace_waterfall, | ||
| ) | ||
| from sentry.seer.fetch_issues import by_error_type, by_function_name, by_text_query, utils | ||
| from sentry.seer.issue_detection import create_issue_occurrence | ||
| from sentry.seer.models.seer_api_models import SeerProjectPreference | ||
|
Check warning on line 119 in src/sentry/seer/endpoints/seer_rpc.py
|
||
| from sentry.seer.utils import filter_repo_by_provider | ||
| from sentry.sentry_apps.metrics import SentryAppEventType | ||
| from sentry.sentry_apps.tasks.sentry_apps import broadcast_webhooks_for_organization | ||
|
|
@@ -565,6 +578,7 @@ | |
| def trigger_coding_agent_launch( | ||
| *, | ||
| organization_id: int, | ||
| project_id: int | None = None, | ||
| integration_id: int, | ||
| run_id: int, | ||
| trigger_source: str = "solution", | ||
|
|
@@ -579,7 +593,7 @@ | |
| trigger_source: Either "root_cause" or "solution" (default: "solution") | ||
|
|
||
| Returns: | ||
| dict: {"success": bool} | ||
| dict: {"success": bool, "error_code": str | None} | ||
| """ | ||
| try: | ||
| launch_coding_agents_for_run( | ||
|
|
@@ -589,7 +603,45 @@ | |
| trigger_source=AutofixTriggerSource(trigger_source), | ||
| ) | ||
| return {"success": True} | ||
| except (NotFound, PermissionDenied, ValidationError, APIException): | ||
| except IntegrationNotFound: | ||
| logger.exception( | ||
| "coding_agent.rpc_launch_error", | ||
| extra={ | ||
| "organization_id": organization_id, | ||
| "integration_id": integration_id, | ||
| "run_id": run_id, | ||
| }, | ||
| ) | ||
| try: | ||
| project = Project.objects.get_from_cache(id=project_id) | ||
|
Comment on lines
+615
to
+616
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: If Suggested FixAdd a guard clause to check if Prompt for AI AgentDid we get this right? 👍 / 👎 to inform future reviews. |
||
| organization = Organization.objects.get_from_cache(id=organization_id) | ||
| if features.has("organizations:seer-project-settings-dual-write", organization): | ||
| preference_response = get_project_seer_preferences(project.id) | ||
| if preference_response and preference_response.preference: | ||
| updated_preference = preference_response.preference.copy( | ||
| update={"automation_handoff": None} | ||
| ) | ||
| validated_pref = SeerProjectPreference.validate(updated_preference) | ||
| resolved_pref = resolve_repository_ids(organization.id, [validated_pref]) | ||
| write_preference_to_sentry_db(project, resolved_pref[0]) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same nit here, just moving
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we leaving the Seer DB pref cleanup on Seer side? Wondering why not reuse |
||
| except Exception: | ||
| logger.exception( | ||
| "coding_agent.clear_handoff_preference_failed", | ||
| extra={ | ||
| "project_id": project_id, | ||
| "organization_id": organization_id, | ||
| "run_id": run_id, | ||
| }, | ||
| ) | ||
| return {"success": False, "error_code": "integration_not_found"} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nullable
|
||
| except ( | ||
| OrganizationNotFound, | ||
| AutofixStateNotFound, | ||
| StateReposNotFound, | ||
| PermissionDenied, | ||
| ValidationError, | ||
| APIException, | ||
| ): | ||
|
sehr-m marked this conversation as resolved.
|
||
| logger.exception( | ||
| "coding_agent.rpc_launch_error", | ||
| extra={ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| from __future__ import annotations | ||
|
|
||
| from unittest.mock import MagicMock, patch | ||
|
|
||
| from sentry.seer.autofix.coding_agent import IntegrationNotFound | ||
| from sentry.seer.autofix.utils import CodingAgentProviderType | ||
| from sentry.seer.models.seer_api_models import SeerAutomationHandoffConfiguration | ||
| from sentry.testutils.cases import TestCase | ||
|
|
||
|
|
||
| class TestTriggerCodingAgentHandoff(TestCase): | ||
| def setUp(self) -> None: | ||
| super().setUp() | ||
| self.organization = self.create_organization() | ||
| self.project = self.create_project(organization=self.organization) | ||
| self.group = self.create_group(project=self.project) | ||
|
|
||
| def _make_handoff_config(self, integration_id: int = 789) -> SeerAutomationHandoffConfiguration: | ||
| return SeerAutomationHandoffConfiguration( | ||
| handoff_point="root_cause", | ||
| target=CodingAgentProviderType.CURSOR_BACKGROUND_AGENT, | ||
| integration_id=integration_id, | ||
| ) | ||
|
|
||
| @patch("sentry.seer.autofix.on_completion_hook.set_project_seer_preference") | ||
| @patch("sentry.seer.autofix.on_completion_hook.get_project_seer_preferences") | ||
| @patch("sentry.seer.autofix.on_completion_hook.trigger_coding_agent_handoff") | ||
| def test_not_found_clears_automation_handoff( | ||
| self, mock_trigger, mock_get_prefs, mock_set_pref | ||
| ) -> None: | ||
| from sentry.seer.autofix.on_completion_hook import AutofixOnCompletionHook | ||
|
|
||
| mock_trigger.side_effect = IntegrationNotFound("Integration not found") | ||
|
|
||
| mock_pref = MagicMock() | ||
| mock_pref.automation_handoff = self._make_handoff_config() | ||
| mock_pref.copy.return_value = mock_pref | ||
| mock_get_prefs.return_value = MagicMock(preference=mock_pref) | ||
|
|
||
| handoff_config = self._make_handoff_config() | ||
|
|
||
| AutofixOnCompletionHook._trigger_coding_agent_handoff( | ||
| organization=self.organization, | ||
| run_id=1, | ||
| group=self.group, | ||
| handoff_config=handoff_config, | ||
| ) | ||
|
|
||
| mock_get_prefs.assert_called_once_with(self.group.project_id) | ||
| mock_pref.copy.assert_called_once_with(update={"automation_handoff": None}) | ||
| mock_set_pref.assert_called_once_with(mock_pref) | ||
|
|
||
| @patch("sentry.seer.autofix.on_completion_hook.set_project_seer_preference") | ||
| @patch("sentry.seer.autofix.on_completion_hook.get_project_seer_preferences") | ||
| @patch("sentry.seer.autofix.on_completion_hook.trigger_coding_agent_handoff") | ||
| def test_not_found_no_preference_response_does_not_call_set( | ||
| self, mock_trigger, mock_get_prefs, mock_set_pref | ||
| ) -> None: | ||
| from sentry.seer.autofix.on_completion_hook import AutofixOnCompletionHook | ||
|
|
||
| mock_trigger.side_effect = IntegrationNotFound("Integration not found") | ||
| mock_get_prefs.return_value = None | ||
|
|
||
| AutofixOnCompletionHook._trigger_coding_agent_handoff( | ||
| organization=self.organization, | ||
| run_id=1, | ||
| group=self.group, | ||
| handoff_config=self._make_handoff_config(), | ||
| ) | ||
|
|
||
| mock_set_pref.assert_not_called() |


Uh oh!
There was an error while loading. Please reload this page.