From 22688cd417a49cc16cf3b25bc975927c56937957 Mon Sep 17 00:00:00 2001 From: Kush Dubey Date: Thu, 2 Apr 2026 13:57:02 -0700 Subject: [PATCH 1/3] ref(seer): Include project slug-id map for repo-project fetch --- src/sentry/seer/endpoints/seer_rpc.py | 24 +++++++++---------- src/sentry/seer/fetch_issues/utils.py | 6 ++++- .../endpoints/test_organization_seer_rpc.py | 2 +- tests/sentry/seer/endpoints/test_seer_rpc.py | 9 ++++--- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/sentry/seer/endpoints/seer_rpc.py b/src/sentry/seer/endpoints/seer_rpc.py index cdabc2867d0bef..ec983725e07746 100644 --- a/src/sentry/seer/endpoints/seer_rpc.py +++ b/src/sentry/seer/endpoints/seer_rpc.py @@ -48,7 +48,6 @@ from sentry.hybridcloud.rpc.service import RpcAuthenticationSetupException, RpcResolutionException from sentry.hybridcloud.rpc.sig import SerializableFunctionValueException from sentry.integrations.github_enterprise.integration import GitHubEnterpriseIntegration -from sentry.integrations.models.repository_project_path_config import RepositoryProjectPathConfig from sentry.integrations.services.integration import integration_service from sentry.integrations.types import IntegrationProviderSlug from sentry.models.organization import Organization, OrganizationStatus @@ -115,6 +114,7 @@ rpc_get_trace_waterfall, ) from sentry.seer.fetch_issues import by_error_type, by_function_name, by_text_query, utils +from sentry.seer.fetch_issues.utils import NoProjectsForRepoError, get_repo_and_projects from sentry.seer.issue_detection import create_issue_occurrence from sentry.seer.models.seer_api_models import SeerProjectPreference from sentry.seer.utils import filter_repo_by_provider @@ -655,7 +655,7 @@ def trigger_coding_agent_launch( def has_repo_code_mappings( *, organization_id: int, provider: SeerSCMProvider, external_id: str, owner: str, name: str -) -> dict[str, bool]: +) -> dict[str, bool | dict[str, int]]: """ Validate that a repository exists and belongs to the given organization. @@ -667,19 +667,17 @@ def has_repo_code_mappings( name: The repository name (e.g., "sentry") Returns: - dict: {"has_code_mappings": bool} + dict: {"has_code_mappings": bool, "project_slug_to_id": dict[str, int]} """ - repo = filter_repo_by_provider(organization_id, provider, external_id, owner, name).first() - - if not repo: - return {"has_code_mappings": False} - - has_mappings = RepositoryProjectPathConfig.objects.filter( - organization_id=organization_id, - repository_id=repo.id, - ).exists() + try: + repo_projects = get_repo_and_projects(organization_id, provider, external_id, owner, name) + except (Repository.DoesNotExist, NoProjectsForRepoError): + return {"has_code_mappings": False, "project_slug_to_id": {}} - return {"has_code_mappings": has_mappings} + project_slug_to_id = dict( + sorted((project.slug, project.id) for project in repo_projects.projects) + ) + return {"has_code_mappings": True, "project_slug_to_id": project_slug_to_id} def validate_repo( diff --git a/src/sentry/seer/fetch_issues/utils.py b/src/sentry/seer/fetch_issues/utils.py index 5411e3839442da..6d071ab9845a35 100644 --- a/src/sentry/seer/fetch_issues/utils.py +++ b/src/sentry/seer/fetch_issues/utils.py @@ -23,6 +23,10 @@ MAX_NUM_DAYS_AGO_DEFAULT = 90 +class NoProjectsForRepoError(Exception): + """Raised when a repo exists but has no Sentry projects via code mappings.""" + + class SeerResponseError(TypedDict): error: str @@ -93,7 +97,7 @@ def get_repo_and_projects( ) projects = [config.project for config in repo_configs] if not projects: - raise ValueError("No Sentry projects found for repo") + raise NoProjectsForRepoError("No Sentry projects found for repo") return RepoProjects( organization_id=organization_id, provider=provider, diff --git a/tests/sentry/seer/endpoints/test_organization_seer_rpc.py b/tests/sentry/seer/endpoints/test_organization_seer_rpc.py index 4a009ebaae7dff..d7430dc4b0ff35 100644 --- a/tests/sentry/seer/endpoints/test_organization_seer_rpc.py +++ b/tests/sentry/seer/endpoints/test_organization_seer_rpc.py @@ -185,4 +185,4 @@ def test_has_repo_code_mappings(self) -> None: ) assert response.status_code == 200 - assert response.data == {"has_code_mappings": False} + assert response.data == {"has_code_mappings": False, "project_slug_to_id": {}} diff --git a/tests/sentry/seer/endpoints/test_seer_rpc.py b/tests/sentry/seer/endpoints/test_seer_rpc.py index 80e6e3e9c369a5..b7a52c6ac6f49c 100644 --- a/tests/sentry/seer/endpoints/test_seer_rpc.py +++ b/tests/sentry/seer/endpoints/test_seer_rpc.py @@ -1004,7 +1004,7 @@ def test_has_repo_code_mappings_repo_not_found(self) -> None: owner="nonexistent", name="nonexistent", ) - assert result == {"has_code_mappings": False} + assert result == {"has_code_mappings": False, "project_slug_to_id": {}} def test_has_repo_code_mappings_no_mappings(self) -> None: """Test when repository exists but has no code mappings""" @@ -1023,7 +1023,7 @@ def test_has_repo_code_mappings_no_mappings(self) -> None: owner="test", name="repo", ) - assert result == {"has_code_mappings": False} + assert result == {"has_code_mappings": False, "project_slug_to_id": {}} def test_has_repo_code_mappings_with_mappings(self) -> None: """Test when repository exists and has code mappings""" @@ -1059,7 +1059,10 @@ def test_has_repo_code_mappings_with_mappings(self) -> None: owner="test", name="repo", ) - assert result == {"has_code_mappings": True} + assert result == { + "has_code_mappings": True, + "project_slug_to_id": {project.slug: project.id}, + } def test_validate_repo_valid(self) -> None: """Test when repository exists and matches all fields""" From 246e93e2e9e1b321ba05419b4c148870cbf566a6 Mon Sep 17 00:00:00 2001 From: Kush Dubey Date: Thu, 2 Apr 2026 14:36:34 -0700 Subject: [PATCH 2/3] fix error ref in test, catch missing proj --- src/sentry/seer/endpoints/seer_rpc.py | 2 +- tests/sentry/seer/fetch_issues/test_utils.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sentry/seer/endpoints/seer_rpc.py b/src/sentry/seer/endpoints/seer_rpc.py index ec983725e07746..321a974f8d26ec 100644 --- a/src/sentry/seer/endpoints/seer_rpc.py +++ b/src/sentry/seer/endpoints/seer_rpc.py @@ -671,7 +671,7 @@ def has_repo_code_mappings( """ try: repo_projects = get_repo_and_projects(organization_id, provider, external_id, owner, name) - except (Repository.DoesNotExist, NoProjectsForRepoError): + except (Repository.DoesNotExist, NoProjectsForRepoError, Project.DoesNotExist): return {"has_code_mappings": False, "project_slug_to_id": {}} project_slug_to_id = dict( diff --git a/tests/sentry/seer/fetch_issues/test_utils.py b/tests/sentry/seer/fetch_issues/test_utils.py index 7b782fdfdf13c1..b607cc8955858c 100644 --- a/tests/sentry/seer/fetch_issues/test_utils.py +++ b/tests/sentry/seer/fetch_issues/test_utils.py @@ -3,6 +3,7 @@ import pytest from sentry.seer.fetch_issues.utils import ( + NoProjectsForRepoError, RepoProjects, as_issue_details, bulk_serialize_for_seer, @@ -75,7 +76,7 @@ def test_get_repo_and_projects_no_configs(self) -> None: external_id="123", ) - with pytest.raises(ValueError, match="No Sentry projects found for repo"): + with pytest.raises(NoProjectsForRepoError, match="No Sentry projects found for repo"): get_repo_and_projects( organization_id=self.organization.id, provider="integrations:github", From 53043bcf0fc8cc9c3a77b941d9ca7cbf9cf59a75 Mon Sep 17 00:00:00 2001 From: Kush Dubey Date: Thu, 2 Apr 2026 14:44:59 -0700 Subject: [PATCH 3/3] handle deletion correctly --- src/sentry/seer/endpoints/seer_rpc.py | 2 +- src/sentry/seer/fetch_issues/utils.py | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/sentry/seer/endpoints/seer_rpc.py b/src/sentry/seer/endpoints/seer_rpc.py index 321a974f8d26ec..ec983725e07746 100644 --- a/src/sentry/seer/endpoints/seer_rpc.py +++ b/src/sentry/seer/endpoints/seer_rpc.py @@ -671,7 +671,7 @@ def has_repo_code_mappings( """ try: repo_projects = get_repo_and_projects(organization_id, provider, external_id, owner, name) - except (Repository.DoesNotExist, NoProjectsForRepoError, Project.DoesNotExist): + except (Repository.DoesNotExist, NoProjectsForRepoError): return {"has_code_mappings": False, "project_slug_to_id": {}} project_slug_to_id = dict( diff --git a/src/sentry/seer/fetch_issues/utils.py b/src/sentry/seer/fetch_issues/utils.py index 6d071ab9845a35..7567811f2c0969 100644 --- a/src/sentry/seer/fetch_issues/utils.py +++ b/src/sentry/seer/fetch_issues/utils.py @@ -95,15 +95,27 @@ def get_repo_and_projects( repository_id=repo.id, ) ) - projects = [config.project for config in repo_configs] + + projects = [] + valid_configs = [] + for config in repo_configs: + try: + project = config.project + except Project.DoesNotExist: + continue + else: + valid_configs.append(config) + projects.append(project) + if not projects: raise NoProjectsForRepoError("No Sentry projects found for repo") + return RepoProjects( organization_id=organization_id, provider=provider, external_id=external_id, repo=repo, - repo_configs=repo_configs, + repo_configs=valid_configs, projects=projects, )