diff --git a/src/sentry/seer/signed_seer_api.py b/src/sentry/seer/signed_seer_api.py index ffe8718d0daa..0ba8fb954f90 100644 --- a/src/sentry/seer/signed_seer_api.py +++ b/src/sentry/seer/signed_seer_api.py @@ -117,7 +117,9 @@ def _resolve_viewer_context( org_id = explicit_org_id if explicit_user_id is not None: - if user_id is not None and user_id != explicit_user_id: + # A service account is its own principal; never re-attribute it to a human. + is_service_account = actor.type == ActorType.SERVICE_ACCOUNT + if (user_id is not None or is_service_account) and user_id != explicit_user_id: logger.warning( "seer.viewer_context_mismatch", extra={ @@ -132,7 +134,7 @@ def _resolve_viewer_context( actor = replace(actor, actor_id=explicit_user_id) elif actor.type == ActorType.AGENT: actor = replace(actor, delegated_user_id=explicit_user_id) - else: + elif not is_service_account: actor = Actor(type=ActorType.USER, actor_id=explicit_user_id) metrics.incr( diff --git a/tests/sentry/seer/test_signed_seer_api.py b/tests/sentry/seer/test_signed_seer_api.py index bc663e930b8d..f80fc3c70e2e 100644 --- a/tests/sentry/seer/test_signed_seer_api.py +++ b/tests/sentry/seer/test_signed_seer_api.py @@ -350,6 +350,28 @@ def test_agent_mismatch_warns_and_keeps_agent(self, mock_logger: MagicMock) -> N "user_id", } + @patch("sentry.seer.signed_seer_api.logger") + def test_service_account_is_not_reattributed_to_user(self, mock_logger: MagicMock) -> None: + ctx = ViewerContext( + actor=Actor( + type=ActorType.SERVICE_ACCOUNT, + actor_id=5, + superuser=SuperuserAccess(expires_at=1234567890), + ), + organization_id=42, + project_id=3, + ) + with viewer_context_scope(ctx): + result = _resolve_viewer_context(SeerViewerContext(organization_id=42, user_id=99)) + + assert isinstance(result, ViewerContext) + assert result.actor.type == ActorType.SERVICE_ACCOUNT + assert result.actor.actor_id == 5 + assert result.actor.superuser is None + assert result.project_id is None + mock_logger.warning.assert_called_once() + assert mock_logger.warning.call_args[1]["extra"]["field"] == "user_id" + @patch("sentry.seer.signed_seer_api.metrics") def test_match_emits_metric_with_project_id(self, mock_metrics: MagicMock) -> None: ctx = ViewerContext(