Skip to content

chore(issues): Prevent assigning issues to deactivated users#115668

Merged
amy-chen23 merged 9 commits into
masterfrom
amyc/issues-deactivated-users
May 18, 2026
Merged

chore(issues): Prevent assigning issues to deactivated users#115668
amy-chen23 merged 9 commits into
masterfrom
amyc/issues-deactivated-users

Conversation

@amy-chen23
Copy link
Copy Markdown
Contributor

Addresses ISWF-2214

Prevents assigning issues to deactivated users for both auto-assignments and manual ones.

@amy-chen23 amy-chen23 requested review from a team and shashjar May 15, 2026 21:07
@github-actions github-actions Bot added the Scope: Backend Automatically applied to PRs that change backend components label May 15, 2026
Comment thread src/sentry/api/helpers/group_index/validators/group.py Outdated
@JoshFerge
Copy link
Copy Markdown
Member

makes sense! just a question for my edification:

OrganizationMember records remain after deactivation, with user_is_active replicated from User.is_active, so assignment validation should account for that.

how does a user get deactivated?

Comment thread src/sentry/models/groupassignee.py Outdated
@amy-chen23
Copy link
Copy Markdown
Contributor Author

makes sense! just a question for my edification:

OrganizationMember records remain after deactivation, with user_is_active replicated from User.is_active, so assignment validation should account for that.

how does a user get deactivated?

only superusers can hard delete accounts. soft deletes (deactivation) is the default where user_is_active is set to false, but the account itself is still in the db

@amy-chen23 amy-chen23 marked this pull request as ready for review May 15, 2026 22:51
@amy-chen23 amy-chen23 requested review from a team as code owners May 15, 2026 22:51
@amy-chen23 amy-chen23 requested a review from a team May 15, 2026 22:51
Comment thread src/sentry/api/helpers/group_index/validators/group.py Outdated
Comment thread src/sentry/types/actor.py
Copy link
Copy Markdown
Contributor

@cvxluo cvxluo left a comment

Choose a reason for hiding this comment

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

looks good, some minor questions

Comment thread tests/sentry/types/test_actor.py Outdated
user = Factories.create_user()
org = Factories.create_organization(owner=user)

OrganizationMember.objects.filter(organization=org, user_id=user.id).update(
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.

nit: why not do user.is_active = False, user.save() instead of filtering and updating?

@@ -950,6 +951,37 @@ def test_post_delete_sets_ownership_version(self) -> None:
assert version is not None
assert isinstance(version, float)

def test_handle_auto_assignment_skips_deactivated_user(self) -> None:
self.ownership = ProjectOwnership.objects.create(
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.

nit, why assign these to self here? do other tests do that here?

Comment thread src/sentry/models/projectownership.py Outdated
Comment on lines +323 to +329
if OrganizationMember.objects.filter(
organization_id=group.project.organization_id,
user_id=owner.id,
user_is_active=False,
).exists():
logger.info("handle_auto_assignment.skip_deactivated_user", extra=logging_extra)
return
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.

why do we filter by inactive owners rather than checking owner.is_active?

Comment thread src/sentry/models/groupassignee.py Outdated
from sentry.users.services.user import RpcUser

if isinstance(assigned_to, (UserModel, RpcUser)):
if OrganizationMember.objects.filter(
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.

Copy link
Copy Markdown
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.

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 426eef7. Configure here.

Comment thread src/sentry/models/projectownership.py
Comment thread src/sentry/models/projectownership.py Outdated
Comment on lines +141 to +142
if isinstance(assigned_to, (UserModel, RpcUser)) and assigned_to.is_active is False:
return {"new_assignment": False, "updated_assignment": False}
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: GroupAssignee.objects.assign() now silently fails for deactivated users, but the integration sync path in sync.py doesn't check the return value, leading to inconsistent assignment states.
Severity: MEDIUM

Suggested Fix

The caller, _handle_assign in sync.py, should check the return value of GroupAssignee.objects.assign(). If the assignment was not successful (e.g., new_assignment is False), the group should not be appended to the groups_assigned list. This will ensure that a partial sync failure is correctly reported when attempting to assign a deactivated user.

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/models/groupassignee.py#L141-L142

Potential issue: The integration sync path in `_handle_assign` calls
`GroupAssignee.objects.assign()` but does not check its return value. With the new
change, if `assign()` is called with a deactivated user, it silently returns without
performing the assignment. However, the caller in `sync.py` unconditionally adds the
group to `groups_assigned`, incorrectly reporting a successful assignment. This prevents
a `lifecycle.record_halt` from being triggered, which should signal a partial sync
failure. Consequently, the external tool (e.g., Jira, GitHub) and Sentry will have
inconsistent assignee states, with the external tool showing an assignment that doesn't
exist in Sentry.

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.

preexisting issue, will create a follow-up ticket for this

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.

@amy-chen23 amy-chen23 merged commit dea950c into master May 18, 2026
81 checks passed
@amy-chen23 amy-chen23 deleted the amyc/issues-deactivated-users branch May 18, 2026 21: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.

4 participants