Skip to content

fix(api): tighten published mutation scopes#113120

Closed
dcramer wants to merge 12 commits into
dcramer/fix/api-readonly-mutation-notesfrom
dcramer/fix/api-write-scope-mutations
Closed

fix(api): tighten published mutation scopes#113120
dcramer wants to merge 12 commits into
dcramer/fix/api-readonly-mutation-notesfrom
dcramer/fix/api-write-scope-mutations

Conversation

@dcramer

@dcramer dcramer commented Apr 15, 2026

Copy link
Copy Markdown
Member

Align published mutation scopes with the write-scope family each feature already belongs to, and document the published mutation endpoints that intentionally remain readonly or member-capable.

The main alternative was to remove all legacy org-scoped alert access immediately. I did not do that for the oldest public metric alert and cron monitor mutation APIs because they are the most likely to have external token clients that still depend on org:write. Everything newer, or clearly part of internal authoring helpers, now moves to the alert-specific write scope directly.

Behavioral Scope Changes

Alerts

  • Shared project-scoped alert mutation checks now use alerts:write only via ALERT_MUTATION_SCOPES.
  • OrganizationAlertRulePermission mutation methods conceptually move from org:write / org:admin / alerts:write to alerts:write.
  • Deprecated public organization metric alert endpoints keep temporary legacy org:write compatibility, tagged with TODO(api-write-scope-compat), on OrganizationAlertRuleIndexEndpoint.POST, OrganizationAlertRuleDetailsEndpoint.PUT, and OrganizationAlertRuleDetailsEndpoint.DELETE.
  • Nested metric alert project validation now accepts project:read, alerts:write, and the temporary legacy org:write compatibility path. org:admin is no longer accepted there.

Cron Monitors

  • Organization monitor create now conceptually moves from org:write / org:admin / alerts:write to alerts:write.
  • Public organization monitor create keeps temporary legacy org:write compatibility, tagged with TODO(api-write-scope-compat), on OrganizationMonitorIndexEndpoint.POST.
  • Nested monitor project validation now accepts project:read, alerts:write, and the temporary legacy org:write compatibility path. org:admin is no longer accepted there.
  • Organization monitor bulk edit now requires alerts:write for every target monitor project.

Workflow Engine Alerts

  • OrganizationWorkflowPermission mutation methods now require alerts:write. org:write and org:admin are removed.
  • This affects workflow create, workflow bulk enable or disable, workflow detail update or delete, and action test fire endpoints that rely on that permission class.

Workflow Engine Detectors

  • OrganizationDetectorPermission mutation methods now require alerts:write. org:write and org:admin are removed.
  • This affects detector detail update or delete and detector bulk enable or disable or delete paths.
  • System-created and user-created detectors now use the same alerts:write requirement. System-created detectors no longer keep a separate org:write path.

Alert Authoring Helpers

  • Organization anomaly preview now requires alerts:write on the requested project. org:write no longer authorizes it, and the existing 403 contract for inaccessible or unknown project ids is preserved.
  • Uptime preview check now requires alerts:write. org:write no longer authorizes it.
  • Uptime assertion suggestions now requires alerts:write. org:write no longer authorizes it.

Published Mutation Note Coverage

This PR also adds explicit readonly_mutation_scope_exceptions notes for published mutation endpoints that intentionally still accept readonly or member scopes. These notes are documentation only and are enforced by the published mutation scope note test.

Search And Member Preference Surfaces

  • OrganizationPinnedSearchPermission.PUT / DELETE
  • OrganizationSearchPermission.POST / PUT / DELETE
  • OrganizationRecentSearchPermission.POST
  • DiscoverSavedQueryPermission.POST / PUT / DELETE
  • ExploreSavedQueryPermission.POST / PUT / DELETE
  • Explore saved query star and star order endpoints
  • Group search view create, visit, star, star order, edit, and delete endpoints
  • Dashboard star order endpoint
  • Insights starred segments endpoints

Onboarding And Project Setup Helpers

  • onboarding continuation email
  • onboarding task state
  • repo path parsing validation
  • experimental org project creation
  • relaxed project details update
  • project ownership update
  • incident update

Integrations And Misc Published Helpers

  • integration setup and edit helpers
  • bulk code mapping suggestions
  • organization auth token create and update
  • webhook signing secret create
  • key transaction create and delete
  • dashboards create, update, and delete

Future Legacy Compat To Remove

TODO(api-write-scope-compat) marks the temporary org:write paths that should be removed after client migration:

  • deprecated organization metric alert create, update, and delete, plus the nested alert rule project validation fallback
  • public organization cron monitor create, plus the nested monitor project validation fallback

There is no temporary org:admin compatibility in this PR. That scope is removed from the changed alert mutation paths instead of being grandfathered.

Every changed behavior in this PR has direct regression coverage for either the allow path, the deny path, or both.

Refs getsentry/getsentry#19897

Tighten published mutation endpoints that already have an obvious existing write-capable scope.

Previously, several write methods accepted readonly scopes like org:read, project:read, or event:read even though they mutate server-side state.

Require the existing write scope for those surfaces instead. Keep session behavior intact, including team-scoped alert access, but stop exposing readonly token writes through these endpoints.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
@github-actions github-actions Bot added the Scope: Backend Automatically applied to PRs that change backend components label Apr 15, 2026
Comment thread src/sentry/replays/endpoints/project_replay_details.py Outdated
Comment thread src/sentry/discover/endpoints/discover_key_transactions.py Outdated
@github-actions

This comment was marked as low quality.

Drop the member-session surfaces that were not actually uncontroversial from this PR.

Dashboards, code mappings, repo path parsing, key transactions, and org auth token updates still rely on broader session behavior today, so tightening them here broke existing member flows. Revert those paths in this branch and keep the safe mutation tightening for incidents, alerts, user issue creation, data export, and replay summary.

Also keep replay delete in the project scope domain so delete remains consistent with the rest of the replay permission class.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
Add an explicit owner for static/app/components/markdownTextArea.tsx so the branch clears the CODEOWNERS coverage check.

The file was missing ownership coverage entirely, which caused the fresh PR run to fail even though the API changes on this branch are backend-only.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
Comment thread src/sentry/replays/endpoints/project_replay_details.py Outdated
Comment thread src/sentry/api/bases/organization.py
Keep the replay permission maps aligned with the current repo style of listing implied scopes explicitly.

This does not change the effective permissions on these endpoints, but it removes a local inconsistency in this PR where replay endpoints relied on hierarchy expansion while nearby scope maps did not.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
@github-actions

Copy link
Copy Markdown
Contributor

Backend Test Failures

Failures on 6da3bca in this run:

tests/sentry/replays/endpoints/test_project_replay_details.py::ProjectReplayDetailsTest::test_delete_replay_from_filestorelog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/replays/endpoints/test_project_replay_details.py:181: in test_delete_replay_from_filestore
    assert response.status_code == 204
E   assert 403 == 204
E    +  where 403 = <Response status_code=403, "application/json">.status_code

Comment thread src/sentry/api/bases/incident.py Outdated
Replay delete is a public mutation, so it should not depend on a project read-style contract. Move the DELETE permission to event write/admin so member sessions keep working while token auth requires a real write scope.

Add direct endpoint tests covering the token contract: event:read is denied and event:write is allowed for replay delete.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
Remove the incident permission change from the existing-write-scope pass. Incident mutation has an older member-access contract tied to project access, so tightening it cleanly needs a dedicated scope decision instead of being folded into the uncontroversial cleanup.

Also drop an unrelated CODEOWNERS hunk so this branch stays focused on backend mutation scope changes.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
Keep replay summary POST on read-level replay scopes and mark it as an explicit readonly-mutation exception, since it derives summary data from existing replay/event data.

Document the preview endpoints that intentionally keep write-aligned permissions because they are part of alert and monitor authoring flows, even though they use POST helper endpoints.

Add replay summary token tests covering the read-scope contract.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
Add endpoint-level regression tests for the scope changes on this branch so each permission update is covered directly.

Cover the write-scope transitions for data export, alert rule creation, project user issues, replay delete, detector updates, and the alert helper endpoints. This keeps the branch from relying on indirect role coverage alone and makes the replay summary readonly exception explicit next to direct token tests.

Refs getsentry/getsentry#19897
Co-Authored-By: OpenAI Codex <noreply@openai.com>
@github-actions

github-actions Bot commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Backend Test Failures

Failures on f4b450b in this run:

tests/sentry/seer/endpoints/test_organization_events_anomalies.py::OrganizationEventsAnomaliesEndpointTest::test_alerts_write_scope_allows_postlog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/seer/endpoints/test_organization_events_anomalies.py:190: in test_alerts_write_scope_allows_post
    assert response.status_code == 200
E   assert 400 == 200
E    +  where 400 = <Response status_code=400, "application/json">.status_code
tests/sentry/data_export/endpoints/test_data_export.py::DataExportTest::test_post_allows_event_write_scope_for_api_keyslog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/data_export/endpoints/test_data_export.py:109: in test_post_allows_event_write_scope_for_api_keys
    assert response.status_code == 201
E   assert 500 == 201
E    +  where 500 = <Response status_code=500, "application/json">.status_code
tests/sentry/incidents/endpoints/test_organization_alert_rule_index.py::AlertRuleCreateEndpointTest::test_create_allows_alerts_write_scope_for_tokenslog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/incidents/endpoints/test_organization_alert_rule_index.py:484: in test_create_allows_alerts_write_scope_for_tokens
    assert response.status_code == 201
E   assert 400 == 201
E    +  where 400 = <Response status_code=400, "application/json">.status_code

Use supported token auth for data export, give alert-rule token tests a valid member project setup, and return a real anomaly payload in the Seer regression test.

These tests were asserting the new permission contracts, but two of them were exercising invalid success paths and one was using an auth shape that the endpoint does not serialize correctly.

Co-Authored-By: Codex <noreply@openai.com>
@github-actions

Copy link
Copy Markdown
Contributor

Backend Test Failures

Failures on eaf7859 in this run:

tests/sentry/incidents/endpoints/test_organization_alert_rule_index.py::AlertRuleCreateEndpointTest::test_create_allows_alerts_write_scope_for_tokenslog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/incidents/endpoints/test_organization_alert_rule_index.py:488: in test_create_allows_alerts_write_scope_for_tokens
    assert response.status_code == 201
E   assert 400 == 201
E    +  where 400 = <Response status_code=400, "application/json">.status_code

Alert and monitor mutations now accept alerts:write at the endpoint level, but the nested project validators still required project:read. That let the request through permission checks and then failed token-based create and update flows with a 400 during validation.

Allow those serializers to validate project selection against the same write-capable scopes the endpoints already advertise, and add direct token regression tests for alert rule updates and monitor creation so the contract stays enforced.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
Limit org-scoped alert and detector mutations to either the target project's alerts:write access or an explicit endpoint opt-in for the older team-scoped fallback. This closes the cross-team mutation path on alert rule and detector details, and adds project-scoped checks for the other shared consumers that still relied on the broad organization permission.

Align replay deletion with the rest of the event delete surface by requiring event:admin instead of event:write. Add regression coverage for the scope changes and the cross-project authorization cases.

Co-Authored-By: Codex <noreply@openai.com>
request.access.has_any_project_scope(monitor.project, ALERT_MUTATION_SCOPES)
for monitor in monitors
):
return self.respond(status=403)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

monitor.project attribute does not exist on Monitor model

High Severity

The Monitor model defines project_id as a BoundedBigIntegerField, not a ForeignKey. This means monitor.project does not exist as an attribute—accessing it will raise an AttributeError at runtime, causing a 500 error on every bulk-edit PUT request to this endpoint. The check needs to look up the Project by monitor.project_id first, or use select_related with a proper FK relationship.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2a711cc. Configure here.

@github-actions

github-actions Bot commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

Backend Test Failures

Failures on 396368f in this run:

tests/sentry/monitors/endpoints/test_organization_monitor_index.py::BulkEditOrganizationMonitorTest::test_bulk_edit_denies_alerts_write_scope_for_other_team_projectslog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/monitors/endpoints/test_organization_monitor_index.py:1001: in test_bulk_edit_denies_alerts_write_scope_for_other_team_projects
    other_monitor = self._create_monitor(project=other_project, slug="other-monitor")
src/sentry/testutils/cases.py:3108: in _create_monitor
    return Monitor.objects.create(
src/sentry/silo/base.py:157: in override
    return original_method(*args, **kwargs)
.venv/lib/python3.13/site-packages/django/db/models/manager.py:87: in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
src/sentry/silo/base.py:157: in override
    return original_method(*args, **kwargs)
.venv/lib/python3.13/site-packages/django/db/models/query.py:663: in create
    obj = self.model(**kwargs)
.venv/lib/python3.13/site-packages/django/db/models/base.py:569: in __init__
    raise TypeError(
E   TypeError: Monitor() got unexpected keyword arguments: 'project'
tests/sentry/monitors/endpoints/test_organization_monitor_index.py::BulkEditOrganizationMonitorTest::test_enable_no_quotalog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/monitors/endpoints/test_organization_monitor_index.py:1030: in test_enable_no_quota
    assert response.status_code == 400
E   assert 500 == 400
E    +  where 500 = <Response status_code=500, "application/json">.status_code
tests/sentry/monitors/endpoints/test_organization_monitor_index.py::BulkEditOrganizationMonitorTest::test_bulk_disable_enablelog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/monitors/endpoints/test_organization_monitor_index.py:967: in test_bulk_disable_enable
    response = self.get_success_response(self.organization.slug, **data)
src/sentry/testutils/cases.py:636: in get_success_response
    assert_status_code(response, status.HTTP_200_OK)
src/sentry/testutils/asserts.py:46: in assert_status_code
    assert minimum <= response.status_code < maximum, response
E   AssertionError: <Response status_code=500, "application/json">
E   assert 500 < 201
E    +  where 500 = <Response status_code=500, "application/json">.status_code
tests/sentry/monitors/endpoints/test_organization_monitor_index.py::BulkEditOrganizationMonitorTest::test_bulk_mute_unmutelog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/monitors/endpoints/test_organization_monitor_index.py:922: in test_bulk_mute_unmute
    response = self.get_success_response(self.organization.slug, **data)
src/sentry/testutils/cases.py:636: in get_success_response
    assert_status_code(response, status.HTTP_200_OK)
src/sentry/testutils/asserts.py:46: in assert_status_code
    assert minimum <= response.status_code < maximum, response
E   AssertionError: <Response status_code=500, "application/json">
E   assert 500 < 201
E    +  where 500 = <Response status_code=500, "application/json">.status_code
tests/sentry/workflow_engine/endpoints/test_organization_detector_details.py::OrganizationDetectorDetailsDeleteTest::test_delete_denies_alerts_write_scope_for_other_team_projectslog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/workflow_engine/endpoints/test_organization_detector_details.py:1047: in test_delete_denies_alerts_write_scope_for_other_team_projects
    token = self._create_token("alerts:write", user=team_admin_user)
E   AttributeError: 'OrganizationDetectorDetailsDeleteTest' object has no attribute '_create_token'. Did you mean: 'create_comment'?
tests/sentry/seer/endpoints/test_organization_events_anomalies.py::OrganizationEventsAnomaliesEndpointTest::test_alerts_write_scope_denies_other_team_projectslog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/seer/endpoints/test_organization_events_anomalies.py:253: in test_alerts_write_scope_denies_other_team_projects
    assert response.status_code == 403
E   assert 400 == 403
E    +  where 400 = <Response status_code=400, "application/json">.status_code
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py::AlertRuleDetailsPutEndpointTest::test_update_denies_alerts_write_scope_for_other_team_projectslog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py:1056: in test_update_denies_alerts_write_scope_for_other_team_projects
    other_alert_rule = self.new_alert_rule(
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/unittest/mock.py:1424: in patched
    return func(*newargs, **newkeywargs)
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py:111: in new_alert_rule
    assert serializer.is_valid(), serializer.errors
E   AssertionError: {'owner': [ErrorDetail(string='User is not a member of this organization', code='invalid')]}
E   assert False
E    +  where False = <bound method BaseSerializer.is_valid of AlertRuleSerializer(context={'organization': <Organization at 0x7f8f5b203890:...ult=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False)>()
E    +    where <bound method BaseSerializer.is_valid of AlertRuleSerializer(context={'organization': <Organization at 0x7f8f5b203890:...ult=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False)> = AlertRuleSerializer(context={'organization': <Organization at 0x7f8f5b203890: id=4557974926852128, owner_id=None, name...ault=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False).is_valid
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py::AlertRuleDetailsDeleteEndpointTest::test_delete_denies_alerts_write_scope_for_other_team_projectslog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py:2663: in test_delete_denies_alerts_write_scope_for_other_team_projects
    other_alert_rule = self.new_alert_rule(
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/unittest/mock.py:1424: in patched
    return func(*newargs, **newkeywargs)
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py:111: in new_alert_rule
    assert serializer.is_valid(), serializer.errors
E   AssertionError: {'owner': [ErrorDetail(string='User is not a member of this organization', code='invalid')]}
E   assert False
E    +  where False = <bound method BaseSerializer.is_valid of AlertRuleSerializer(context={'organization': <Organization at 0x7fb608ad2c10:...ult=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False)>()
E    +    where <bound method BaseSerializer.is_valid of AlertRuleSerializer(context={'organization': <Organization at 0x7fb608ad2c10:...ult=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False)> = AlertRuleSerializer(context={'organization': <Organization at 0x7fb608ad2c10: id=4557974926786576, owner_id=None, name...ault=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False).is_valid

@dcramer dcramer changed the title fix(api): require existing write scopes for published mutations fix(api): tighten published mutation scopes Apr 16, 2026
Normalize RpcUserOrganizationContext to the underlying organization before resolving target projects for alert-rule and detector mutation permissions. Without that, the new project-scoped alerts:write path breaks during organization object-permission checks because those hooks run before convert_args swaps in the concrete organization.

Add team-admin session regressions that force the project-scoped alerts:write authorization path for alert rule and detector updates and deletes, including the workflow-engine fake-detector alert-rule route.

Co-Authored-By: Codex <noreply@openai.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 34ef4d0. Configure here.

if isinstance(organization, RpcUserOrganizationContext):
return organization.organization.id

return organization.id

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate _get_organization_id helper across two files

Low Severity

The _get_organization_id function is identically defined in both bases.py and organization_detector_details.py. This duplication increases the risk that a future fix to one copy won't be applied to the other. It could be extracted to a shared utility.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 34ef4d0. Configure here.

@github-actions

github-actions Bot commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

Backend Test Failures

Failures on 962a11e in this run:

tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py::AlertRuleDetailsPutEndpointTest::test_team_admin_can_update_with_project_scoped_alerts_writelog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py:1022: in test_team_admin_can_update_with_project_scoped_alerts_write
    reverse(self.endpoint, args=[self.organization.slug, self.alert_rule.id]),
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/functools.py:1039: in __get__
    val = self.func(instance)
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py:135: in alert_rule
    return self.new_alert_rule(data=deepcopy(self.alert_rule_dict))
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/unittest/mock.py:1424: in patched
    return func(*newargs, **newkeywargs)
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py:111: in new_alert_rule
    assert serializer.is_valid(), serializer.errors
E   AssertionError: {'owner': [ErrorDetail(string='User is not a member of this organization', code='invalid')]}
E   assert False
E    +  where False = <bound method BaseSerializer.is_valid of AlertRuleSerializer(context={'organization': <Organization at 0x7f43ae65d950:...ult=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False)>()
E    +    where <bound method BaseSerializer.is_valid of AlertRuleSerializer(context={'organization': <Organization at 0x7f43ae65d950:...ult=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False)> = AlertRuleSerializer(context={'organization': <Organization at 0x7f43ae65d950: id=4557975124705296, owner_id=None, name...ault=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False).is_valid
tests/sentry/monitors/endpoints/test_organization_monitor_index.py::BulkEditOrganizationMonitorTest::test_bulk_disable_enablelog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/monitors/endpoints/test_organization_monitor_index.py:967: in test_bulk_disable_enable
    response = self.get_success_response(self.organization.slug, **data)
src/sentry/testutils/cases.py:636: in get_success_response
    assert_status_code(response, status.HTTP_200_OK)
src/sentry/testutils/asserts.py:46: in assert_status_code
    assert minimum <= response.status_code < maximum, response
E   AssertionError: <Response status_code=500, "application/json">
E   assert 500 < 201
E    +  where 500 = <Response status_code=500, "application/json">.status_code
tests/sentry/monitors/endpoints/test_organization_monitor_index.py::BulkEditOrganizationMonitorTest::test_bulk_mute_unmutelog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/monitors/endpoints/test_organization_monitor_index.py:922: in test_bulk_mute_unmute
    response = self.get_success_response(self.organization.slug, **data)
src/sentry/testutils/cases.py:636: in get_success_response
    assert_status_code(response, status.HTTP_200_OK)
src/sentry/testutils/asserts.py:46: in assert_status_code
    assert minimum <= response.status_code < maximum, response
E   AssertionError: <Response status_code=500, "application/json">
E   assert 500 < 201
E    +  where 500 = <Response status_code=500, "application/json">.status_code
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py::AlertRuleDetailsPutEndpointTest::test_update_denies_alerts_write_scope_for_other_team_projectslog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py:1078: in test_update_denies_alerts_write_scope_for_other_team_projects
    other_alert_rule = self.new_alert_rule(
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/unittest/mock.py:1424: in patched
    return func(*newargs, **newkeywargs)
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py:111: in new_alert_rule
    assert serializer.is_valid(), serializer.errors
E   AssertionError: {'owner': [ErrorDetail(string='User is not a member of this organization', code='invalid')]}
E   assert False
E    +  where False = <bound method BaseSerializer.is_valid of AlertRuleSerializer(context={'organization': <Organization at 0x7f52a0514690:...ult=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False)>()
E    +    where <bound method BaseSerializer.is_valid of AlertRuleSerializer(context={'organization': <Organization at 0x7f52a0514690:...ult=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False)> = AlertRuleSerializer(context={'organization': <Organization at 0x7f52a0514690: id=4557975124508688, owner_id=None, name...ault=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False).is_valid
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py::AlertRuleDetailsDeleteEndpointTest::test_delete_denies_alerts_write_scope_for_other_team_projectslog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py:2706: in test_delete_denies_alerts_write_scope_for_other_team_projects
    other_alert_rule = self.new_alert_rule(
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/unittest/mock.py:1424: in patched
    return func(*newargs, **newkeywargs)
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py:111: in new_alert_rule
    assert serializer.is_valid(), serializer.errors
E   AssertionError: {'owner': [ErrorDetail(string='User is not a member of this organization', code='invalid')]}
E   assert False
E    +  where False = <bound method BaseSerializer.is_valid of AlertRuleSerializer(context={'organization': <Organization at 0x7fba276b56d0:...ult=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False)>()
E    +    where <bound method BaseSerializer.is_valid of AlertRuleSerializer(context={'organization': <Organization at 0x7fba276b56d0:...ult=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False)> = AlertRuleSerializer(context={'organization': <Organization at 0x7fba276b56d0: id=4557975124574240, owner_id=None, name...ault=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False).is_valid
tests/sentry/monitors/endpoints/test_organization_monitor_index.py::BulkEditOrganizationMonitorTest::test_bulk_edit_denies_alerts_write_scope_for_other_team_projectslog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/monitors/endpoints/test_organization_monitor_index.py:1001: in test_bulk_edit_denies_alerts_write_scope_for_other_team_projects
    other_monitor = self._create_monitor(project=other_project, slug="other-monitor")
src/sentry/testutils/cases.py:3108: in _create_monitor
    return Monitor.objects.create(
src/sentry/silo/base.py:157: in override
    return original_method(*args, **kwargs)
.venv/lib/python3.13/site-packages/django/db/models/manager.py:87: in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
src/sentry/silo/base.py:157: in override
    return original_method(*args, **kwargs)
.venv/lib/python3.13/site-packages/django/db/models/query.py:663: in create
    obj = self.model(**kwargs)
.venv/lib/python3.13/site-packages/django/db/models/base.py:569: in __init__
    raise TypeError(
E   TypeError: Monitor() got unexpected keyword arguments: 'project'
tests/sentry/monitors/endpoints/test_organization_monitor_index.py::BulkEditOrganizationMonitorTest::test_enable_no_quotalog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/monitors/endpoints/test_organization_monitor_index.py:1030: in test_enable_no_quota
    assert response.status_code == 400
E   assert 500 == 400
E    +  where 500 = <Response status_code=500, "application/json">.status_code
tests/sentry/workflow_engine/endpoints/test_organization_detector_details.py::OrganizationDetectorDetailsDeleteTest::test_delete_denies_alerts_write_scope_for_other_team_projectslog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/workflow_engine/endpoints/test_organization_detector_details.py:1087: in test_delete_denies_alerts_write_scope_for_other_team_projects
    token = self._create_token("alerts:write", user=team_admin_user)
E   AttributeError: 'OrganizationDetectorDetailsDeleteTest' object has no attribute '_create_token'. Did you mean: 'create_comment'?
tests/sentry/seer/endpoints/test_organization_events_anomalies.py::OrganizationEventsAnomaliesEndpointTest::test_alerts_write_scope_denies_other_team_projectslog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/seer/endpoints/test_organization_events_anomalies.py:253: in test_alerts_write_scope_denies_other_team_projects
    assert response.status_code == 403
E   assert 400 == 403
E    +  where 400 = <Response status_code=400, "application/json">.status_code

@dcramer

dcramer commented Apr 16, 2026

Copy link
Copy Markdown
Member Author

my local env is having some issues that i need to fix so cant run tests fully locall yet

did a ton of cleanup and audit here, but ill flip it to review as soon as i feel confident that it needs more eyes

@dcramer

dcramer commented Apr 16, 2026

Copy link
Copy Markdown
Member Author

im rebasing this on main in #113194

@dcramer dcramer closed this Apr 16, 2026
@github-actions github-actions Bot locked and limited conversation to collaborators May 2, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Scope: Backend Automatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants