Repository navigation
Erase group entries a config merge recreated after another device erased them - #2233
Merged
mpretty-cyro merged 3 commits intoOct 8, 2026
Merged
Conversation
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 devicecatches 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 ownUserGroupsentry 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_diffcreates 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.mergeUserConfigserases these entries under the same lock as the merge, so nothing reads one back as a group, then finishes throughremoveGroup, whose write is dumped and pushed (a merge write is neither pushed byConfigUploadernor fully dumped). The rule isClosedGroupInfo.isErasedGroupStub().Tested:
ErasedGroupStubTest. AppiumDelete group linked deviceon 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 ondev(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.