A community learned from another device kept its token as the title - #2202
Merged
mpretty-cyro merged 2 commits intoAug 31, 2026
Merged
Conversation
A community that syncs to a linked device showed its raw room token as the
conversation title, permanently. The device that joined showed the name.
Joining fetches the room outright and stores its details before writing the
community into config (OpenGroupManager.add). A linked device only ever receives
that config, so it never runs that fetch -- and add() cannot help it, because the
config arriving is how it learned about the community, so the already-joined
short-circuit is always taken.
That leaves the poller, which asks for pollInfo/{infoUpdates} and therefore gets
the room's details ONLY if the room's counter differs from the number we send.
With nothing cached we send 0, and a room whose metadata has not been edited
since it was created still has a counter of 0 -- setting a name at creation does
not bump it, so this is not limited to freshly minted test rooms. The server
answers "no change", we store a record with no details, we keep sending 0, and
the title never changes. The blank name then falls back to the room token, which
RecipientNames marks as a last resort.
So while we have no usable name for a room, ask for the room outright instead of
polling it.
The condition is "no name", not "no row" and not "no details object", because
neither of those can express this: patchRoomInfo is an unconditional
INSERT OR REPLACE, so a row exists after the first poll regardless, and
RoomInfo.details is non-nullable with an empty default, so a response carrying no
details deserialises to a present-but-blank object rather than to null. A row
check would fire once and then go quiet; a null-details check would never fire at
all. Keying on the name also matches the render exactly -- a blank name is
precisely the state that shows the token -- so devices already holding a nameless
record are repaired rather than left as they are.
Cost is one extra request per poll cycle for a room that has never yielded a
name, including the edge case of a room genuinely named "". A room with a name
makes the request once and then never again.
mpretty-cyro
marked this pull request as ready for review
August 31, 2026 06:12
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.
A community that reaches a device by config sync shows its raw room token as the conversation title, permanently.
What the user sees
Join a community on one device. On every other device the user owns, the conversation is titled
qa-0797141d-423a-484e-801f-d0fc7737b48a-w0-1instead of its name. The device that joined shows the name correctly. Messages sync fine — only the title is wrong, and nothing the user can do in the app changes it.This is not limited to freshly created test rooms. PySOGS's own tests assert that a room created with a name has
info_updates == 0(tests/test_rooms.py:86-101); only a later metadata edit moves it to 1. So any community whose metadata has not been edited since it was created — the normal state of a small community — reproduces this.Why
The community's name is not part of the synced config record. The record carries base URL, room token and pubkey; the name is fetched from the SOGS by each device and stored locally, and the title is
roomInfo?.details?.name ?: room(RecipientNames.kt:22).The joining device fetches it:
OpenGroupManager.add()calls the room-details API before writing the community into config. A device that learns of the community from that config can never reach that fetch —add()short-circuits onalreadyJoined, and the config arriving is how it learned about the community at all.That leaves the poller, which is gated:
With nothing cached the device sends 0; the room is also at 0; the server reports no change; the poll writes a row with no details; the device keeps sending 0. Forever, and unaffected by time or by opening the conversation.
The change
While the poller has no usable name for a room, ask for the room outright instead of polling it. One branch, at the point that already knows what it has cached.
The predicate is "no usable name", not "no row" and not "no details object" — neither of those can express this state:
patchRoomInfois an unconditionalINSERT OR REPLACE, so a row exists after the first poll whether details came back or not. A row check fires once and then goes quiet, leaving every already-affected device stuck.RoomInfo.detailsis non-nullable with an empty default (OpenGroupApi.kt:102), so a details-less response deserialises to a present-but-blank object. Adetails == nullcheck compiles, reads as if it does something, and never fires.It also matches the render: a blank name is exactly the state that falls back to the token, so the refetch condition and the visible symptom are the same condition. It repairs devices already holding a nameless record, and stops permanently once a name arrives. A failed request never reaches
patchRoomInfo, so nothing is written and the next poll retries.Verification
Join community test @androidpasses twice at 28s, against a 148s timeout before. Confirmed against a build grepped for the change. The room's counter was read directly from the SOGS database mid-run to confirminfo_updates=0on a room created with its name.No unit test. There is no existing test harness for
OpenGroupPollerorCommunityDatabase, and the change is one predicate inside a poll loop needing the API executor, config factory, database and a server. The Appium spec is what covers it.For comparison
iOS has the same gate in its poller and would hit the same dead end; it never depends on it, because a community arriving from config triggers
performInitialRequestsAfterAdd, which does a full room fetch (LibSession+UserGroups.swift:73-94). That is fire-and-forget (Task.detached+try?), so an iOS device whose fetch fails is stuck exactly as Android was, with no retry. This fix retries until a name arrives.