-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
ref(seer): Remove should_run_automation from get_issue_summary #112219
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
ef45afc
aefbfde
49cc43c
8194e69
b2998a8
f708a56
a913659
8dd528b
359fcc0
ab1e9fa
901ed71
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 |
|---|---|---|
|
|
@@ -520,15 +520,13 @@ def _generate_summary( | |
| group: Group, | ||
| user: User | RpcUser | AnonymousUser, | ||
| force_event_id: str | None, | ||
| source: SeerAutomationSource, | ||
| cache_key: str, | ||
| should_run_automation: bool = True, | ||
| ) -> tuple[dict[str, Any], int]: | ||
| ) -> tuple[dict[str, Any], int, GroupEvent | None]: | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| """Core logic to generate and cache the issue summary.""" | ||
| serialized_event, event = _get_event(group, user, provided_event_id=force_event_id) | ||
|
|
||
| if not serialized_event or not event: | ||
| return {"detail": "Could not find an event for the issue"}, 400 | ||
| return {"detail": "Could not find an event for the issue"}, 400, None | ||
|
|
||
| trace_tree = None | ||
| if event: | ||
|
|
@@ -571,15 +569,7 @@ def _generate_summary( | |
| summary_dict["event_id"] = event.event_id | ||
| cache.set(cache_key, summary_dict, timeout=int(timedelta(days=7).total_seconds())) | ||
|
|
||
| if should_run_automation: | ||
| try: | ||
| run_automation(group, user, event, source) | ||
| except Exception: | ||
| logger.exception( | ||
| "Error auto-triggering autofix from issue summary", extra={"group_id": group.id} | ||
| ) | ||
|
|
||
| return summary_dict, 200 | ||
| return summary_dict, 200, event | ||
|
Member
Author
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. Moved the automation responsibility to the calling spot, it was getting too complicated tracing all the various paths and implicit behavior.
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. We can't remove this here. The old Seer SKU (not per seat orgs) still use this flow.
Member
Author
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. I think that should still be preserved. The |
||
|
|
||
|
|
||
| def _log_seer_scanner_billing_event(group: Group, source: SeerAutomationSource): | ||
|
|
@@ -604,8 +594,7 @@ def get_issue_summary( | |
| user: User | RpcUser | AnonymousUser | None = None, | ||
| force_event_id: str | None = None, | ||
| source: SeerAutomationSource = SeerAutomationSource.ISSUE_DETAILS, | ||
| should_run_automation: bool = True, | ||
| ) -> tuple[dict[str, Any], int]: | ||
| ) -> tuple[dict[str, Any], int, GroupEvent | None]: | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| """ | ||
| Generate an AI summary for an issue. | ||
|
|
||
|
|
@@ -614,18 +603,17 @@ def get_issue_summary( | |
| user: The user requesting the summary | ||
| force_event_id: Optional event ID to force summarizing a specific event | ||
| source: The source triggering the summary generation | ||
| should_run_automation: Whether to trigger automation after generating summary | ||
|
cursor[bot] marked this conversation as resolved.
|
||
|
|
||
| Returns: | ||
| A tuple containing (summary_data, status_code) | ||
| A tuple containing (summary_data, status_code, event) | ||
| """ | ||
| if user is None: | ||
| user = AnonymousUser() | ||
| if not features.has("organizations:gen-ai-features", group.organization, actor=user): | ||
| return {"detail": "Feature flag not enabled"}, 400 | ||
| return {"detail": "Feature flag not enabled"}, 400, None | ||
|
|
||
| if group.organization.get_option("sentry:hide_ai_features"): | ||
| return {"detail": "AI features are disabled for this organization."}, 403 | ||
| return {"detail": "AI features are disabled for this organization."}, 403, None | ||
|
|
||
| cache_key = get_issue_summary_cache_key(group.id) | ||
| lock_key, lock_name = get_issue_summary_lock_key(group.id) | ||
|
|
@@ -634,15 +622,13 @@ def get_issue_summary( | |
|
|
||
| # if force_event_id is set, we always generate a new summary | ||
| if force_event_id: | ||
| summary_dict, status_code = _generate_summary( | ||
| group, user, force_event_id, source, cache_key, should_run_automation | ||
| ) | ||
| summary_dict, status_code, event = _generate_summary(group, user, force_event_id, cache_key) | ||
| _log_seer_scanner_billing_event(group, source) | ||
| return convert_dict_key_case(summary_dict, snake_to_camel_case), status_code | ||
| return convert_dict_key_case(summary_dict, snake_to_camel_case), status_code, event | ||
|
|
||
| # 1. Check cache first | ||
| if cached_summary := cache.get(cache_key): | ||
| return convert_dict_key_case(cached_summary, snake_to_camel_case), 200 | ||
| return convert_dict_key_case(cached_summary, snake_to_camel_case), 200, None | ||
|
|
||
| # 2. Try to acquire lock | ||
| try: | ||
|
|
@@ -653,17 +639,17 @@ def get_issue_summary( | |
| # Re-check cache after acquiring lock, in case another process finished | ||
| # while we were waiting for the lock. | ||
| if cached_summary := cache.get(cache_key): | ||
| return convert_dict_key_case(cached_summary, snake_to_camel_case), 200 | ||
| return convert_dict_key_case(cached_summary, snake_to_camel_case), 200, None | ||
|
|
||
| # Lock acquired and cache is still empty, proceed with generation | ||
| summary_dict, status_code = _generate_summary( | ||
| group, user, force_event_id, source, cache_key, should_run_automation | ||
| summary_dict, status_code, event = _generate_summary( | ||
| group, user, force_event_id, cache_key | ||
| ) | ||
| _log_seer_scanner_billing_event(group, source) | ||
| return convert_dict_key_case(summary_dict, snake_to_camel_case), status_code | ||
| return convert_dict_key_case(summary_dict, snake_to_camel_case), status_code, event | ||
|
|
||
| except UnableToAcquireLock: | ||
| # Failed to acquire lock within timeout. Check cache one last time. | ||
| if cached_summary := cache.get(cache_key): | ||
| return convert_dict_key_case(cached_summary, snake_to_camel_case), 200 | ||
| return {"detail": "Timeout waiting for summary generation lock"}, 503 | ||
| return convert_dict_key_case(cached_summary, snake_to_camel_case), 200, None | ||
| return {"detail": "Timeout waiting for summary generation lock"}, 503, None | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -74,7 +74,9 @@ def check_autofix_status(run_id: int, organization_id: int) -> None: | |
| retry=Retry(times=1), | ||
| ) | ||
| def generate_summary_and_run_automation(group_id: int, **kwargs) -> None: | ||
| from sentry.seer.autofix.issue_summary import get_issue_summary | ||
| from django.contrib.auth.models import AnonymousUser | ||
|
|
||
| from sentry.seer.autofix.issue_summary import get_issue_summary, run_automation | ||
|
Comment on lines
+77
to
+79
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. do these imports need to be inlined or can they go to top of file?
Member
Author
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.
|
||
|
|
||
| trigger_path = kwargs.get("trigger_path", "unknown") | ||
| sentry_sdk.set_tag("trigger_path", trigger_path) | ||
|
|
@@ -98,7 +100,12 @@ def generate_summary_and_run_automation(group_id: int, **kwargs) -> None: | |
| ) | ||
| ) | ||
|
|
||
| get_issue_summary(group=group, source=SeerAutomationSource.POST_PROCESS) | ||
| _, status_code, event = get_issue_summary(group=group, source=SeerAutomationSource.POST_PROCESS) | ||
| if status_code == 200 and event is not None: | ||
| try: | ||
| run_automation(group, AnonymousUser(), event, SeerAutomationSource.POST_PROCESS) | ||
| except Exception: | ||
| logger.exception("seer.automation.run_automation_failed", extra={"group_id": group.id}) | ||
|
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. I think this issue warden caught is correct.
Member
Author
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. From what I can tell this issue is present in the old code as well. which early returns before the automation gets triggered. We can fix if you think this is a bug but trying to keep this PR having no behavior changes.
Member
Author
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. Although to Warden's point maybe this should be named
sentry[bot] marked this conversation as resolved.
|
||
|
|
||
|
|
||
| @instrumented_task( | ||
|
|
@@ -107,7 +114,7 @@ def generate_summary_and_run_automation(group_id: int, **kwargs) -> None: | |
| processing_deadline_duration=35, | ||
| retry=Retry(times=3, delay=3, on=(Exception,)), | ||
| ) | ||
| def generate_issue_summary_only(group_id: int) -> None: | ||
| def generate_summary_and_fixability_score(group_id: int) -> None: | ||
|
Member
Author
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. Can revert the naming if you find it confusing to not match the task name, but the |
||
| """ | ||
| Generate issue summary WITHOUT triggering automation. | ||
| Used for triage signals flow when event count < AUTOFIX_AUTOMATION_OCCURRENCE_THRESHOLD or when summary doesn't exist yet. | ||
|
|
@@ -137,9 +144,7 @@ def generate_issue_summary_only(group_id: int) -> None: | |
| ) | ||
|
|
||
| # Generate and cache the summary | ||
| get_issue_summary( | ||
| group=group, source=SeerAutomationSource.POST_PROCESS, should_run_automation=False | ||
| ) | ||
| get_issue_summary(group=group, source=SeerAutomationSource.POST_PROCESS) | ||
|
|
||
| get_and_update_group_fixability_score(group, force_generate=True) | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alert path runs automation without timeout protection
Medium Severity
The
run_automationcall, which makes HTTP requests to the Seer API, now runs synchronously on the main thread after the timed issue summary generation. This work is no longer covered by the timeout, allowing slow Seer responses to block the alert notification thread.Reviewed by Cursor Bugbot for commit 901ed71. Configure here.