Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/sentry/snuba/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,10 @@ def _create_snql_in_snuba(
) -> str:
body = {
"project_id": subscription.project_id,
# Snuba attributes the resulting queries to this organization. Metrics entities
# additionally send a legacy `organization` key via get_entity_extra_params,
# which their subscription processors are configured with.
"organization_id": subscription.project.organization_id,
Comment on lines +279 to +282

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Bug: A deployment order dependency could cause subscription creation to fail if Snuba rejects the new organization_id field before it's updated to support it.
Severity: MEDIUM

Suggested Fix

To mitigate the deployment order risk, consider adding a feature flag or another form of forward compatibility check. This would allow the new organization_id field to be sent only when the corresponding Snuba version is known to be deployed, preventing subscription creation failures if Sentry is deployed first.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: src/sentry/snuba/tasks.py#L279-L282

Potential issue: The code adds an `organization_id` field to the payload for creating
Snuba subscriptions, which depends on a corresponding update in the Snuba service. If
this Sentry code is deployed before the Snuba update, Snuba might reject requests
containing the unknown field with a 4xx error. This would cause the
`_create_snql_in_snuba` function to raise a `SnubaError`, leading to the failure of the
subscription creation task. As a result, new alert subscriptions would fail to be
created and remain in a "CREATING" state until the Snuba service is updated.

Did we get this right? 👍 / 👎 to inform future reviews.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Verified against current Snuba master (dffa2b529) — this is a false positive, and Sentry-first deploy is safe.

SnQLSubscriptionData.from_dict has no allowlist rejection path. Unrecognized keys are swept into metadata rather than refused:

for key in data:
    if key not in SUBSCRIPTION_DATA_PAYLOAD_KEYS:
        metadata[key] = data[key]

The /subscriptions endpoint does no JSON-schema validation of the body either — it just calls SubscriptionDataCodec.decode. I ran the payload this PR sends against unmodified Snuba master:

OK   events:           metadata={'organization_id': 4567}
OK   metrics_counters: metadata={'organization_id': 4567, 'organization': 4567}

So no 4xx, no SnubaError, no subscriptions stuck in CREATING. Old Snuba silently ignores the field, which makes this inert-until-supported rather than breaking.

Deploy order still matters for correctness (not safety): until getsentry/snuba#8400 lands, Snuba ignores organization_id for the events entity, so attribution keeps using the placeholder. That PR also adds an AddColumnCondition fallback so the metrics entities accept either key. It should merge first, but a Sentry-first deploy degrades gracefully rather than failing.

"query": str(snql_query.query),
"time_window": snuba_query.time_window,
"resolution": snuba_query.resolution,
Expand Down
7 changes: 7 additions & 0 deletions tests/sentry/snuba/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,13 @@ def test_status_join(self) -> None:
assert sub.status == QuerySubscription.Status.ACTIVE.value
assert sub.subscription_id is not None

def test_organization_id(self) -> None:
sub = self.create_subscription(QuerySubscription.Status.CREATING)
with patch.object(_snuba_pool, "urlopen", side_effect=_snuba_pool.urlopen) as urlopen:
create_subscription_in_snuba(sub.id)
request_body = json.loads(urlopen.call_args[1]["body"])
assert request_body["organization_id"] == self.organization.id

def test_group_id(self) -> None:
group_id = 1234
sub = self.create_subscription(
Expand Down
Loading