feat(ACI): Make OrganizationAlertRuleDetails DELETE method backwards compatible#109845
feat(ACI): Make OrganizationAlertRuleDetails DELETE method backwards compatible#109845
Conversation
src/sentry/workflow_engine/endpoints/organization_detector_details.py
Outdated
Show resolved
Hide resolved
src/sentry/workflow_engine/endpoints/organization_detector_details.py
Outdated
Show resolved
Hide resolved
| except Detector.DoesNotExist: | ||
| raise ResourceDoesNotExist | ||
|
|
||
| return args, kwargs |
There was a problem hiding this comment.
Feature-flagged orgs lose access to unmigrated alert rules
Medium Severity
When workflow-engine-rule-serializers is enabled, convert_args has no fallback to look up an AlertRule directly. If an alert rule exists but hasn't been migrated (no AlertRuleDetector row), the AlertRuleDetector lookup fails, then get_object_id_from_fake_id produces a negative number, and the Detector lookup also fails, resulting in a ResourceDoesNotExist (404). A valid, unmigrated alert rule becomes completely inaccessible through this endpoint.
There was a problem hiding this comment.
This seems perfectly ok. Unmigrated rules shouldn't exist, and where they do, we seem to intend to ignore them.
Backend Test FailuresFailures on
|
src/sentry/incidents/endpoints/organization_alert_rule_details.py
Outdated
Show resolved
Hide resolved
Backend Test FailuresFailures on
|
saponifi3d
left a comment
There was a problem hiding this comment.
just super nits, lgtm! ![]()
src/sentry/incidents/endpoints/organization_alert_rule_details.py
Outdated
Show resolved
Hide resolved
|
|
||
|
|
||
| class WorkflowEngineOrganizationAlertRuleEndpoint(OrganizationAlertRuleEndpoint): | ||
| def convert_args( |
There was a problem hiding this comment.
Not for this pr / yet, but this code looks fairly similar to the convert args on WorkflowEngineOrganizationRuleEndpoint -- if we have another instance of this, it might be worth refactoring a little to pull the shared logic out.
src/sentry/incidents/endpoints/organization_alert_rule_details.py
Outdated
Show resolved
Hide resolved
| raise PermissionDenied | ||
|
|
||
| validator = get_detector_validator(request, detector.project, detector.type, instance=detector) | ||
| validator.delete() |
There was a problem hiding this comment.
Don't we want to do this async so the DataSources can get cleaned up too?
I realize this is copied code, but.. a concern.
There was a problem hiding this comment.
The base class schedules the deletion and marks it as pending https://github.com/getsentry/sentry/blob/master/src/sentry/workflow_engine/endpoints/validators/base/detector.py#L221-L231
I added a data source and data source detector to the test to make sure they're removed
There was a problem hiding this comment.
ah! I totally missed that this was the validator, not the detector >_<
carry on.
|
|
||
| return Response(status=status.HTTP_204_NO_CONTENT) | ||
| except AlreadyDeletedError: | ||
| return Response( |
There was a problem hiding this comment.
Interesting, I'd expect us to return a 204 or maybe a 404 if it is already deleted.
src/sentry/incidents/endpoints/organization_alert_rule_details.py
Outdated
Show resolved
Hide resolved
| if features.has("organizations:workflow-engine-rule-serializers", organization): | ||
| try: | ||
| ard = AlertRuleDetector.objects.get( | ||
| alert_rule_id=validated_alert_rule_id, | ||
| detector__project__organization=organization, | ||
| ) | ||
| kwargs["alert_rule"] = ard.detector |
There was a problem hiding this comment.
Bug: With the workflow-engine-rule-serializers flag on, GET/PUT requests for dual-written alert rules incorrectly resolve to a Detector and fail with a 400 error.
Severity: HIGH
Suggested Fix
Update the fetch_alert_rule and update_alert_rule methods to correctly handle Detector objects that are resolved from an alert_rule_id, similar to how the DELETE method handles them. This will prevent the endpoint from incorrectly returning a 400 error for valid, dual-written alert rules.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: src/sentry/incidents/endpoints/bases.py#L125-L131
Potential issue: When the `organizations:workflow-engine-rule-serializers` feature flag
is enabled, the `convert_args` method resolves alert rule IDs for dual-written rules
into `Detector` objects. However, the GET and PUT endpoint methods that receive this
object are implemented to reject `Detector` instances and return a 400 error. This
causes requests to fetch or update these specific alert rules to fail for any
organization with the feature flag active, breaking a core workflow.
| if isinstance(alert_rule, Detector): | ||
| project = alert_rule.project | ||
| else: | ||
| # check to see if there's a project associated with the alert rule | ||
| project = alert_rule.projects.get() |
There was a problem hiding this comment.
Bug: A missing .select_related("project") when fetching Detector objects in convert_args causes an extra database query per request when project access is checked.
Severity: MEDIUM
Suggested Fix
Add .select_related("project") to the queries that fetch Detector instances in the convert_args method. This applies to both the lookup via AlertRuleDetector.objects.get() and the direct lookup via Detector.objects.get() to ensure the project is pre-fetched and avoid the extra query.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: src/sentry/incidents/endpoints/organization_alert_rule_details.py#L309-L313
Potential issue: In `WorkflowEngineOrganizationAlertRuleEndpoint.convert_args`, when a
`Detector` instance is fetched, it is done without pre-fetching the related project via
`select_related("project")`. Subsequently, the `_check_project_access` decorator
accesses `alert_rule.project`, which triggers an additional, unnecessary database query
to fetch the project for each request. This introduces a performance regression for all
GET, PUT, and DELETE requests that handle a `Detector` object.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| except AlreadyDeletedError: | ||
| return Response( | ||
| "This rule has already been deleted", status=status.HTTP_400_BAD_REQUEST | ||
| ) |
There was a problem hiding this comment.
Atomic transaction can roll back successful detector deletion
Medium Severity
The Detector path in remove_alert_rule wraps both remove_detector and delete_alert_rule in a single transaction.atomic block. If delete_alert_rule raises any uncaught exception (e.g., an unexpected DB error), the entire transaction rolls back — including the detector deletion that remove_detector already completed. The code's intent is to always delete the detector and optionally clean up the linked AlertRule (as evidenced by catching AlertRuleDetector.DoesNotExist and AlertRule.DoesNotExist to still return 204). Wrapping both in one transaction contradicts that intent, since a failure deleting the AlertRule also undoes the detector deletion, potentially leaving the user unable to delete the detector on retry if the alert rule issue persists.
| except AlreadyDeletedError: | ||
| return Response( | ||
| "This rule has already been deleted", status=status.HTTP_400_BAD_REQUEST | ||
| ) |
There was a problem hiding this comment.
Unreachable AlreadyDeletedError handler rolls back detector deletion
Low Severity
The except AlreadyDeletedError handler on the Detector path is effectively unreachable. AlreadyDeletedError is only raised by delete_alert_rule when alert_rule.status == AlertRuleStatus.SNAPSHOT.value, but AlertRule.objects.get() uses the AlertRuleManager which excludes SNAPSHOT-status rules. Since Django model instances don't auto-refresh, the in-memory status will never be SNAPSHOT at the point delete_alert_rule checks it. This dead handler adds misleading code and, if somehow reached, would roll back the detector deletion — returning a 400 "already deleted" error even though the detector has not been deleted.
kcons
left a comment
There was a problem hiding this comment.
ok, I think the bots may have a point in one or two of these cases, but I'll let you resolve that.


Make the delete an alert rule method backwards compatible by accepting a detector ID and using the
OrganizationDetectorDetailsEndpointdelete strategy. This also implements a new base class andconvert_argsto look up either a detector or an alert rule, and removes some old code using the feature flag from before we needed this to work in the UI. For now, this just makes the other methods fail if a detector is passed in.