feat(scm): Add SCM subscriptions platform publisher#107441
feat(scm): Add SCM subscriptions platform publisher#107441
Conversation
| "reopened", | ||
| "review_request_removed", | ||
| "review_requested", | ||
| ] |
There was a problem hiding this comment.
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)
src/sentry/scm/helpers.py
Outdated
| ) | ||
| if not integration: | ||
| raise SCMCodedError(code="integration_not_found") | ||
| raise SCMCodedError(code="unsupported_integration") |
There was a problem hiding this comment.
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.
| "reopened", | ||
| "review_request_removed", | ||
| "review_requested", | ||
| ] |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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_foundSCM error code and now raise it when integration lookup returns no record, while preservingunsupported_integrationfor unsupported providers.
- Restored a distinct
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):
src/sentry/scm/helpers.py
Outdated
| ) | ||
| if not integration: | ||
| raise SCMCodedError(code="integration_not_found") | ||
| raise SCMCodedError(code="unsupported_integration") |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.



What it does:
Introduces a new event streaming infrastructure for SCM webhooks:
Core Architecture:
decorator
Integration Changes:
Modified webhook handlers across all SCM providers:
Each now calls publish_subscription_event() to emit events into the stream instead of
direct processing.
Why:
Decouples webhook processing from business logic:
Technical highlights: