Skip to content

Erase group entries a config merge recreated after another device erased them - #2233

Merged
mpretty-cyro merged 3 commits into
session-foundation:devfrom
mpretty-cyro:fix/erased-group-stub
Oct 8, 2026
Merged

mpretty-cyro merged 3 commits into
session-foundation:devfrom
mpretty-cyro:fix/erased-group-stub

Conversation

@mpretty-cyro

@mpretty-cyro mpretty-cyro commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

When an admin deletes a group, the admin's own linked device shows the member "Group has been deleted by a group admin" state instead of the conversation going away, and the group then comes back on the device that deleted it (as "Unknown Group" on iOS, with the deleted preview elsewhere). The Appium spec Delete group linked device catches it: in the legacy release regression Android failed it every run and iOS intermittently.

Cause. The deleting device destroys the group's info, pushes it, and only then erases the group from UserGroups. A linked device therefore usually sees the destroyed info first and marks its own UserGroups entry destroyed. libsession merges the two diverged configs by replaying each diff; when the erase is applied first, the destroyed-flag diff recreates the erased key holding only that field (apply_diff creates the missing dict, src/config.cpp). The result is an entry with the removed status and no name or keys, and it syncs to both devices.

Fix. A real kicked or destroyed entry always keeps its name, so a group entry that is kicked or destroyed with no name, no admin key and no auth data can only be that leftover. It is erased while handling the merge and never turned into a conversation; a conversation still held for it goes through the existing "no longer in the config" removal. Other admins keep their full entry and still see the deleted state. The same rule goes into all three clients; the libsession merge behaviour itself is being looked at separately.

Android: ConfigFactory.mergeUserConfigs erases these entries under the same lock as the merge, so nothing reads one back as a group, then finishes through removeGroup, whose write is dumped and pushed (a merge write is neither pushed by ConfigUploader nor fully dumped). The rule is ClosedGroupInfo.isErasedGroupStub().

Tested: ErasedGroupStubTest. Appium Delete group linked device on devnet (API 37 emulators): 5/6 pass across two runs of this branch (3/3, then 2/3 after the push/dump change below), against 0/3 on dev (the same spec failed every run in the release regression). The one failure is a different ordering: the deleting device merged the linked device's destroyed write before its own erase had been pushed, which leaves a complete destroyed entry (so it shows the group as deleted, like a member) rather than a nameless stub; the rule deliberately doesn't touch that.

Same fix on the other clients: session-foundation/session-ios#812, #2233, session-foundation/session-desktop#2027.

…ased them

When an admin deletes a group, the deleting device destroys the group's info, pushes it, then erases
the group from UserGroups. A linked device usually sees the destroyed info first and marks its
UserGroups entry destroyed. libsession merges the two diverged configs by replaying each diff, and if
the erase applies first the destroyed-flag diff recreates the erased key holding only that field: an
entry with the removed status and no name or keys. That entry then syncs back, so the group reappears
on the deleting device ("Unknown Group" on iOS, the "deleted by a group admin" preview elsewhere) and
stays on the linked device.

A real kicked or destroyed entry always keeps its name, so a removed entry with no name, no admin key
and no auth data can only be this. Erase it as part of handling the merge and never build a
conversation from it; any conversation we still have for it is removed through the normal path. Other
admins keep their full entry and still see the deleted state.

The same rule is applied on iOS, Android and Desktop. The underlying merge behaviour is tracked
separately for libsession.
The erase ran inside the merge's write, which ConfigUploader doesn't push (it drops fromMerge
notifications) and which only dumps the merged config type: the UserGroups erase waited for some
later local change to be pushed, and the convo-info-volatile erase could be lost on restart. Finish
through removeGroup, whose write dumps and pushes like any local change; the erase inside the merge
stays so nothing reads the stub back first.

Also state the name assumption as the convention it is: libsession's header calls the name
invite-only.
@mpretty-cyro
mpretty-cyro marked this pull request as ready for review October 8, 2026 02:25
@mpretty-cyro
mpretty-cyro merged commit 5a4a257 into session-foundation:dev Oct 8, 2026
7 of 9 checks passed
@mpretty-cyro
mpretty-cyro deleted the fix/erased-group-stub branch October 8, 2026 02:26
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