Skip to content

Fix: a duplicate group invite demoted a member who had already joined - #2218

Open
mpretty-cyro wants to merge 1 commit into
session-foundation:devfrom
mpretty-cyro:fix/duplicate-invite-demotes-member
Open

mpretty-cyro wants to merge 1 commit into
session-foundation:devfrom
mpretty-cyro:fix/duplicate-invite-demotes-member

Conversation

@mpretty-cyro

Copy link
Copy Markdown
Collaborator

Issue #2215, reported with a reliable repro and against every Session Android version.

Receiving side

GroupManagerV2Impl.handleInvitation had no check for a group we are already in. It built a fresh ClosedGroupInfo and wrote it over the existing record, so a second invitation set invited back to true and reset joinedAtSecs to 0 — a joined member was demoted to "invited" and their join time was lost.

handlePromotion already guards this case with if (group == null), and the invitation path now does the same thing — but conditionally, not literally. A literal group == null check would break re-invitation after a kick: handleKicked keeps the record with kicked = true, and an invitation that rebuilds it is the only way back in. The guard is therefore "already joined" — present, and not invited, kicked or destroyed.

Admin side

inviteMembersInternal called setInvited() whatever state the member was in, so re-inviting someone who had already accepted reset them in the group members config too. It now skips that for INVITE_ACCEPTED and PROMOTION_ACCEPTED. Other promotion states are left exactly as they were.

Tests

GroupManagerV2ImplTest, synthetic group config only:

  • a second invitation for a joined group writes nothing at all (verified at dangerouslyAccessMutableUserConfigs, so it covers both the membership flag and joinedAtSecs)
  • a first invitation still writes, with invited = true — a control, so the negative test above cannot pass by never reaching the code

The first test was verified to fail with the guard reverted, reproducing the reported behaviour; the control passes either way, which is what makes the negative meaningful.

The admin half has no test: GroupMember's state setters are native, so a JVM unit test cannot reach them.

Unit suite: 300 pass, 0 fail (298 on dev plus these two).

Issue session-foundation#2215, reported against every Session Android version. handleInvitation built a
fresh ClosedGroupInfo and wrote it over the existing record, so a second invite for a
group we were already in set us back to invited and discarded joinedAtSecs. It now
mirrors the check handlePromotion already makes, and still rebuilds when we were
kicked or the group was destroyed - a fresh invitation is how we get back in.

The admin side of the same defect: inviteMembersInternal called setInvited() whatever
state the member was in, so re-inviting someone who had accepted reset them in the
group members config too.

The regression test covers the receiving side only; GroupMember's setters are native,
so a JVM unit test cannot reach the admin path.
@mpretty-cyro mpretty-cyro self-assigned this Sep 20, 2026
@mpretty-cyro
mpretty-cyro marked this pull request as ready for review September 20, 2026 20:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant