Skip to content

feat(scm): Add SCM subscriptions platform publisher#107441

Merged
cmanallen merged 232 commits intomasterfrom
cmanallen/scm-platform-subscription-consumer
Mar 9, 2026
Merged

feat(scm): Add SCM subscriptions platform publisher#107441
cmanallen merged 232 commits intomasterfrom
cmanallen/scm-platform-subscription-consumer

Conversation

@cmanallen
Copy link
Member

@cmanallen cmanallen commented Feb 2, 2026

What it does:

Introduces a new event streaming infrastructure for SCM webhooks:

Core Architecture:

  • New SourceCodeManagerEventStream class (src/sentry/scm/private/event_stream.py):
  • Event listener registration via @scm_event_stream.listen_for(event_type="...")
    decorator
  • Supports 3 event types: check runs, comments, pull requests
  • Singleton pattern for global event stream access
  • Asynchronous task queue execution for listeners
  • Built-in metrics/performance tracking

Integration Changes:
Modified webhook handlers across all SCM providers:

  • Bitbucket
  • GitHub
  • GitHub Enterprise
  • GitLab

Each now calls publish_subscription_event() to emit events into the stream instead of
direct processing.

Why:

Decouples webhook processing from business logic:

  • ✅ Vendor-agnostic event handling
  • ✅ Multiple listeners per event type
  • ✅ Type-safe interfaces
  • ✅ Easier testing and extensibility
  • ✅ Move from monolithic handlers to pub/sub pattern

Technical highlights:

  • Decorator-based registration makes adding new listeners simple
  • Type safety ensures correct event payloads
  • Async task execution prevents webhook timeout issues
  • Standardized event publishing across all providers

@cmanallen cmanallen changed the base branch from master to cmanallen/scm-platform February 2, 2026 21:11
@github-actions github-actions bot added the Scope: Backend Automatically applied to PRs that change backend components label Feb 2, 2026
"reopened",
"review_request_removed",
"review_requested",
]
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing common GitHub PR action causes validation errors

Medium Severity

PullRequestAction is missing "synchronize", one of the most frequently sent GitHub pull_request webhook actions (fires on every commit push to a PR). The msgspec decoder for GitHubPullRequestEvent uses this literal type, so every synchronize event will fail deserialization. These failures are caught by produce_event_to_scm_stream but each one calls sentry_sdk.capture_exception, which will generate significant noise once the rollout flag is enabled.

Additional Locations (1)

Fix in Cursor Fix in Web

)
if not integration:
raise SCMCodedError(code="integration_not_found")
raise SCMCodedError(code="unsupported_integration")
Copy link
Contributor

Choose a reason for hiding this comment

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

Wrong error code for missing integration lookup

Low Severity

fetch_service_provider raises SCMCodedError(code="unsupported_integration") when integration_service.get_integration() returns None. The integration isn't unsupported — it simply doesn't exist in the database. The error message "An unsupported integration provider was found" is misleading for this case. The rename from "integration_not_found" fixed the semantics at line 69 (truly unsupported provider) but made it incorrect here.

Fix in Cursor Fix in Web

Base automatically changed from cmanallen/scm-platform to master March 9, 2026 13:42
@cmanallen cmanallen requested a review from a team as a code owner March 9, 2026 13:42
"reopened",
"review_request_removed",
"review_requested",
]
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing common GitHub PR actions causes decoding failures

High Severity

PullRequestAction is missing "synchronize" and several other common GitHub pull request webhook actions. Since GitHubPullRequestEvent uses this Literal type for msgspec decoding, any GitHub PR event with an unlisted action (like "synchronize", sent on every push to a PR branch) will raise a msgspec.ValidationError. This error propagates to produce_event_to_scm_stream, which catches it, reports it to Sentry, and records a failure metric — generating high-volume noise for one of GitHub's most common webhook events.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link
Contributor

@cursor cursor bot left a comment

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.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Wrong error code for missing integration lookup
    • Restored a distinct integration_not_found SCM error code and now raise it when integration lookup returns no record, while preserving unsupported_integration for unsupported providers.

Create PR

Or push these changes by commenting:

@cursor push ea91f3898b
Preview (ea91f3898b)
diff --git a/src/sentry/scm/errors.py b/src/sentry/scm/errors.py
--- a/src/sentry/scm/errors.py
+++ b/src/sentry/scm/errors.py
@@ -3,6 +3,7 @@
 type ErrorCode = Literal[
     "repository_inactive",
     "repository_not_found",
+    "integration_not_found",
     "repository_organization_mismatch",
     "rate_limit_exceeded",
     "unsupported_integration",
@@ -11,6 +12,7 @@
 ERROR_CODES: dict[ErrorCode, str] = {
     "repository_inactive": "A repository was found but it is inactive.",
     "repository_not_found": "A repository could not be found.",
+    "integration_not_found": "An integration could not be found.",
     "repository_organization_mismatch": "A repository was found but it did not belong to your organization.",
     "rate_limit_exceeded": "Exhausted allocated service-provider quota.",
     "unsupported_integration": "An unsupported integration provider was found.",

diff --git a/src/sentry/scm/helpers.py b/src/sentry/scm/helpers.py
--- a/src/sentry/scm/helpers.py
+++ b/src/sentry/scm/helpers.py
@@ -81,16 +81,16 @@
 def fetch_service_provider(
     organization_id: int,
     repository: Repository,
-    map_to_provider: Callable[[Integration | RpcIntegration, int, Repository], Provider] = lambda i,
-    oid,
-    r: map_integration_to_provider(oid, i, r),
+    map_to_provider: Callable[
+        [Integration | RpcIntegration, int, Repository], Provider
+    ] = lambda i, oid, r: map_integration_to_provider(oid, i, r),
 ) -> Provider:
     integration = integration_service.get_integration(
         integration_id=repository["integration_id"],
         organization_id=organization_id,
     )
     if not integration:
-        raise SCMCodedError(code="unsupported_integration")
+        raise SCMCodedError(code="integration_not_found")
 
     return map_to_provider(integration, organization_id, repository)
 

diff --git a/tests/sentry/scm/integration/test_helpers.py b/tests/sentry/scm/integration/test_helpers.py
--- a/tests/sentry/scm/integration/test_helpers.py
+++ b/tests/sentry/scm/integration/test_helpers.py
@@ -185,7 +185,7 @@
         with pytest.raises(SCMCodedError) as exc_info:
             fetch_service_provider(self.organization.id, repository)
 
-        assert exc_info.value.code == "unsupported_integration"
+        assert exc_info.value.code == "integration_not_found"
 
 
 class TestIsRateLimited(TestCase):
This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

)
if not integration:
raise SCMCodedError(code="integration_not_found")
raise SCMCodedError(code="unsupported_integration")
Copy link
Contributor

Choose a reason for hiding this comment

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

Wrong error code for missing integration lookup

Medium Severity

In fetch_service_provider, when integration_service.get_integration() returns None (integration not found in the database), the code raises SCMCodedError(code="unsupported_integration"). The error message "An unsupported integration provider was found" is misleading — nothing was found at all. The old code used "integration_not_found" which was removed during the rename instead of keeping both codes.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link
Contributor

@cursor cursor bot left a comment

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.

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

@cmanallen cmanallen merged commit f43c4c5 into master Mar 9, 2026
76 checks passed
@cmanallen cmanallen deleted the cmanallen/scm-platform-subscription-consumer branch March 9, 2026 16:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants