Skip to content

fix(cells): Org member serialization returns empty email if user and email fields not available"#116871

Merged
GabeVillalobos merged 4 commits into
masterfrom
gv/org_member_serializer_fixes
Jun 5, 2026
Merged

fix(cells): Org member serialization returns empty email if user and email fields not available"#116871
GabeVillalobos merged 4 commits into
masterfrom
gv/org_member_serializer_fixes

Conversation

@GabeVillalobos

Copy link
Copy Markdown
Member

Adds handling to org member serialization when the backing control user is missing, causing the serialization to fail. We can't always guarantee we have a user object due to eventually consistent deletions, and potential drift.

Relates to INFRENG-263

@GabeVillalobos GabeVillalobos requested a review from a team as a code owner June 4, 2026 17:19
@linear-code

linear-code Bot commented Jun 4, 2026

Copy link
Copy Markdown

INFRENG-263

@github-actions github-actions Bot added the Scope: Backend Automatically applied to PRs that change backend components label Jun 4, 2026

@cursor cursor Bot left a comment

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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

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

Reviewed by Cursor Bugbot for commit 6a23c31. Configure here.

Comment thread src/sentry/api/serializers/models/organization_member/base.py
# control-silo User cannot be resolved (deleted, replication lag),
# fall back to the outbox-denormalized `user_email` and finally to
# an empty string so the response still serializes.
email = serialized_user["email"] if serialized_user else obj.email or ""

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: The serializer incorrectly falls back to obj.email instead of the denormalized obj.user_email for deleted users, causing the API to return an empty email.
Severity: MEDIUM

Suggested Fix

Update the fallback logic to prioritize obj.user_email before obj.email. The expression should be changed to email = serialized_user["email"] if serialized_user else (obj.user_email or obj.email or "") to respect the intended fallback chain.

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/api/serializers/models/organization_member/base.py#L107

Potential issue: When a user associated with an `OrganizationMember` is deleted, the
serializer is supposed to fall back to the denormalized `user_email` field to retrieve
the last known email. However, the code at line 107 incorrectly bypasses
`obj.user_email` and falls back directly to `obj.email`, which is the invite email and
often empty. This causes the API to return an empty string for the email field, even
when valid data exists in `obj.user_email`. This leads to silent data loss, particularly
in distributed systems with eventual consistency.

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

# control-silo User cannot be resolved (deleted, replication lag),
# fall back to the outbox-denormalized `user_email` and finally to
# an empty string so the response still serializes.
email = serialized_user["email"] if serialized_user else obj.email or ""

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is sort of an unusual situation. Ideally, we'd want to prefilter this situation instead of still allowing it to serialize a dead org membership, so that's a possible alternative here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think using the state we do have even if it is stale is better than returning a result that will 'flap' in the future when viewed again.

@GabeVillalobos GabeVillalobos requested a review from a team June 4, 2026 17:22
# control-silo User cannot be resolved (deleted, replication lag),
# fall back to the outbox-denormalized `user_email` and finally to
# an empty string so the response still serializes.
email = serialized_user["email"] if serialized_user else obj.email or ""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think using the state we do have even if it is stale is better than returning a result that will 'flap' in the future when viewed again.

Comment thread tests/sentry/core/endpoints/test_organization_member_index.py Outdated
Co-authored-by: Mark Story <mark@mark-story.com>
@GabeVillalobos GabeVillalobos merged commit a6e5214 into master Jun 5, 2026
64 checks passed
@GabeVillalobos GabeVillalobos deleted the gv/org_member_serializer_fixes branch June 5, 2026 16:34
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.

2 participants