From 4f295061b9f1c3bdee0c8202a7679340bcf5e3b9 Mon Sep 17 00:00:00 2001 From: Dan Fuller Date: Wed, 15 Apr 2026 10:03:58 -0700 Subject: [PATCH 1/2] chore(integrations): Gate disabling repositories behind a separate flag Being cautious with this auto sync work and gating a potentially destructive operation behind a deletion flag. --- src/sentry/features/temporary.py | 1 + .../integrations/github/tasks/sync_repos_on_install_change.py | 2 +- src/sentry/integrations/source_code_management/sync_repos.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sentry/features/temporary.py b/src/sentry/features/temporary.py index 3b957c7c1dce1d..17450cf4383568 100644 --- a/src/sentry/features/temporary.py +++ b/src/sentry/features/temporary.py @@ -149,6 +149,7 @@ def register_temporary_features(manager: FeatureManager) -> None: manager.add("organizations:github-repo-auto-sync", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False) manager.add("organizations:github-repo-auto-sync-apply", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False) manager.add("organizations:github-repo-auto-sync-webhook", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False) + manager.add("organizations:scm-repo-auto-sync-removal", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False) manager.add("organizations:github_enterprise-repo-auto-sync", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False) manager.add("organizations:github_enterprise-repo-auto-sync-apply", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False) manager.add("organizations:gitlab-repo-auto-sync", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False) diff --git a/src/sentry/integrations/github/tasks/sync_repos_on_install_change.py b/src/sentry/integrations/github/tasks/sync_repos_on_install_change.py index 45c32bece3a210..6209ac332703f3 100644 --- a/src/sentry/integrations/github/tasks/sync_repos_on_install_change.py +++ b/src/sentry/integrations/github/tasks/sync_repos_on_install_change.py @@ -140,7 +140,7 @@ def _sync_repos_for_org( provider=integration.provider, ) - if repos_removed: + if repos_removed and features.has("organizations:scm-repo-auto-sync-removal", rpc_org): # Look up repos before disabling to get their IDs and names external_ids = [str(repo["id"]) for repo in repos_removed] existing_repos = repository_service.get_repositories( diff --git a/src/sentry/integrations/source_code_management/sync_repos.py b/src/sentry/integrations/source_code_management/sync_repos.py index bc0832f9bd80e3..69c4deea9523dc 100644 --- a/src/sentry/integrations/source_code_management/sync_repos.py +++ b/src/sentry/integrations/source_code_management/sync_repos.py @@ -247,7 +247,7 @@ def sync_repos_for_org(organization_integration_id: int) -> None: provider=provider_key, ) - if removed_ids: + if removed_ids and _has_feature("organizations:scm-repo-auto-sync-removal", rpc_org): repository_service.disable_repositories_by_external_ids( organization_id=organization_id, integration_id=integration.id, From 2736102f8757bc9294cb5252a5d3c66f6243a628 Mon Sep 17 00:00:00 2001 From: Dan Fuller Date: Wed, 15 Apr 2026 10:51:21 -0700 Subject: [PATCH 2/2] fix tests --- .../test_sync_repos_on_install_change.py | 5 +++-- .../integrations/github/test_webhook.py | 20 +++++++++++++++++-- .../source_code_management/test_sync_repos.py | 6 +++++- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/tests/sentry/integrations/github/tasks/test_sync_repos_on_install_change.py b/tests/sentry/integrations/github/tasks/test_sync_repos_on_install_change.py index 390184025afcce..6f1d16fa6feffd 100644 --- a/tests/sentry/integrations/github/tasks/test_sync_repos_on_install_change.py +++ b/tests/sentry/integrations/github/tasks/test_sync_repos_on_install_change.py @@ -13,6 +13,7 @@ from sentry.testutils.silo import assume_test_silo_mode, assume_test_silo_mode_of, control_silo_test FEATURE_FLAG = "organizations:github-repo-auto-sync-webhook" +REMOVAL_FLAG = "organizations:scm-repo-auto-sync-removal" @control_silo_test @@ -70,7 +71,7 @@ def test_repos_removed(self, _: MagicMock) -> None: status=ObjectStatus.ACTIVE, ) - with self.feature(FEATURE_FLAG): + with self.feature([FEATURE_FLAG, REMOVAL_FLAG]): sync_repos_on_install_change( integration_id=self.integration.id, action="removed", @@ -100,7 +101,7 @@ def test_mixed_add_and_remove(self, _: MagicMock) -> None: status=ObjectStatus.ACTIVE, ) - with self.feature(FEATURE_FLAG): + with self.feature([FEATURE_FLAG, REMOVAL_FLAG]): sync_repos_on_install_change( integration_id=self.integration.id, action="added", diff --git a/tests/sentry/integrations/github/test_webhook.py b/tests/sentry/integrations/github/test_webhook.py index 27db21f9c7979c..ee4676ecd0bb48 100644 --- a/tests/sentry/integrations/github/test_webhook.py +++ b/tests/sentry/integrations/github/test_webhook.py @@ -435,7 +435,15 @@ def test_end_to_end_repos_added(self) -> None: ) sha1, sha256 = self._compute_signatures(body) - with self.feature("organizations:github-repo-auto-sync-webhook"), self.tasks(): + with ( + self.feature( + [ + "organizations:github-repo-auto-sync-webhook", + "organizations:scm-repo-auto-sync-removal", + ] + ), + self.tasks(), + ): response = self.client.post( path=self.url, data=body, @@ -483,7 +491,15 @@ def test_end_to_end_repos_removed(self, mock_seer_cleanup: MagicMock) -> None: ) sha1, sha256 = self._compute_signatures(body) - with self.feature("organizations:github-repo-auto-sync-webhook"), self.tasks(): + with ( + self.feature( + [ + "organizations:github-repo-auto-sync-webhook", + "organizations:scm-repo-auto-sync-removal", + ] + ), + self.tasks(), + ): response = self.client.post( path=self.url, data=body, diff --git a/tests/sentry/integrations/source_code_management/test_sync_repos.py b/tests/sentry/integrations/source_code_management/test_sync_repos.py index c33ceb824a26f9..ceddafb981a40f 100644 --- a/tests/sentry/integrations/source_code_management/test_sync_repos.py +++ b/tests/sentry/integrations/source_code_management/test_sync_repos.py @@ -85,7 +85,11 @@ def test_disables_removed_repos(self, _: MagicMock) -> None: self._add_repos_response([{"id": 1, "full_name": "getsentry/sentry", "name": "sentry"}]) with self.feature( - ["organizations:github-repo-auto-sync", "organizations:github-repo-auto-sync-apply"] + [ + "organizations:github-repo-auto-sync", + "organizations:github-repo-auto-sync-apply", + "organizations:scm-repo-auto-sync-removal", + ] ): sync_repos_for_org(self.oi.id)