ref(gitlab): Wrap status sync delete+create in transaction.atomic()#112490
ref(gitlab): Wrap status sync delete+create in transaction.atomic()#112490iamrajjoshi wants to merge 1 commit intoraj/gitlab-feat-par/status-sync-outboundfrom
Conversation
Ensure the IntegrationExternalProject delete-then-recreate sequence in update_organization_config is atomic. Without this, a partial failure during the create loop would leave the org with no external project records and status sync silently broken. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Atomic transaction targets wrong database connection
- Added router import and specified using=router.db_for_write(IntegrationExternalProject) to ensure the transaction.atomic() block targets the control database where the @control_silo_model is routed.
Or push these changes by commenting:
@cursor push 8fa1afb848
Preview (8fa1afb848)
diff --git a/src/sentry/integrations/gitlab/integration.py b/src/sentry/integrations/gitlab/integration.py
--- a/src/sentry/integrations/gitlab/integration.py
+++ b/src/sentry/integrations/gitlab/integration.py
@@ -6,7 +6,7 @@
from urllib.parse import urlparse
from django import forms
-from django.db import transaction
+from django.db import router, transaction
from django.http.request import HttpRequest
from django.http.response import HttpResponseBase
from django.urls import reverse
@@ -356,7 +356,7 @@
data["sync_status_forward"] = bool(project_mappings)
- with transaction.atomic():
+ with transaction.atomic(using=router.db_for_write(IntegrationExternalProject)):
IntegrationExternalProject.objects.filter(
organization_integration_id=self.org_integration.id
).delete()This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.
Reviewed by Cursor Bugbot for commit ece1db2. Configure here.
| if statuses["on_unresolve"] not in valid_statuses: | ||
| raise IntegrationError( | ||
| f"Invalid unresolve status: {statuses['on_unresolve']}. Must be 'opened' or 'closed'." | ||
| with transaction.atomic(): |
There was a problem hiding this comment.
Atomic transaction targets wrong database connection
High Severity
The transaction.atomic() block defaults to the default database, but IntegrationExternalProject is a @control_silo_model routed to the control database. This mismatch means the delete and create operations are not atomic. If a create fails, the preceding delete won't roll back, leading to data loss. The router import is also missing.
Reviewed by Cursor Bugbot for commit ece1db2. Configure here.
Backend Test FailuresFailures on
|



Summary
IntegrationExternalProjectdelete-then-recreate sequence inupdate_organization_configwithtransaction.atomic()to prevent data loss if acreate()fails mid-loopFollowup to #107216 per @GabeVillalobos's review.
Test plan
test_update_organization_config_status_sync,test_update_organization_config_invalid_status, andtest_update_organization_config_missing_statustests cover the happy path and error cases