fix(cells): Org member serialization returns empty email if user and email fields not available"#116871
Conversation
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.
Reviewed by Cursor Bugbot for commit 6a23c31. Configure here.
| # 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 "" |
There was a problem hiding this comment.
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 "" |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
| # 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 "" |
There was a problem hiding this comment.
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.
Co-authored-by: Mark Story <mark@mark-story.com>

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