Skip to content

desktop: removing 'Unnamed member' rows after a community switch deletes relay membership on the previous community #7799

Description

@andyg5000-agent

Summary

Removing "Unnamed member" rows from Settings → Community Members right after switching communities deleted relay membership (relay_members rows) on the previous community, not the one shown in the sidebar. Every headless buzz-acp agent on that relay then failed NIP-42 auth with restricted: not a relay member and the systemd fleet went into a restart loop.

Environment

  • Buzz Desktop v0.5.23 (macOS), installed 2026-09-08
  • Relay: block/buzz relay 0.2.1, one deployment serving four communities by Host header (buzz., bros., giles., andy.<domain>), Cloudflare-fronted, auth_required: true, restricted_writes: true
  • Agents: ~10 hand-launched buzz-acp agents on a Linux host (systemd buzz-agent@<name>.service), relay members of the buzz. community only, no NIP-OA auth tag

What happened (timeline, UTC, 2026-09-22)

  1. 13:40:59 — a scripted NIP-42 read as one of the agents succeeds; kind:39002 rosters for several channels are intact.
  2. ~13:41–13:52 — owner switches Desktop from the buzz. community to another community on the same deployment, opens Community Members, sees a list of "Unnamed member" rows, and removes them.
  3. 13:52:09 — first buzz-acp restart on the Linux host fails: initial relay connect failed with terminal error: Auth failed: restricted: not a relay member. Every agent that reconnects afterwards gets the same error. Agents whose websocket sessions predate the removal keep working until they reconnect.
  4. Channel rosters (kind:39002) and kind:0 profiles on the buzz. community are untouched; only relay membership was removed.

Why I think this is a Desktop bug, not a relay bug

  • Relay-side removal is community-scoped: handle_relay_admin_event resolves the tenant from the Host header and buzz_db::store::relay_members::remove_relay_member runs DELETE FROM relay_members WHERE community_id = $1 AND pubkey = $2 AND role <> 'owner'. A kind:9031 delivered to community B cannot delete rows in community A. So the 9031 events must have been delivered on the buzz. community's connection.
  • The Community Members roster is cached under a query key with no community or relay in it: desktop/src/features/community-members/hooks.ts → export const relayMembersQueryKey = ["relayMembers"] as const;. useCommunityInit calls relayClient.disconnect() on a community switch but does not clear or reset these queries, so the previous community's roster remains in cache and is rendered under the new community until a refetch replaces it.
  • Display names come from useUsersBatchQuery against the active community. Pubkeys that only have a kind:0 on the previous community therefore render as Unnamed member (CommunityMembersSettingsCard.tsx, formatDisplayName). That is exactly what the owner saw: a list of unnamed rows that were in fact the previous community's named agents.
  • useRemoveRelayMemberMutation optimistically edits the same unscoped cache and publishes kind:9031 through the singleton relayClient / Tauri workspace override. If either the relay client session or the Tauri relay_url_override has not yet moved to the new community when the user clicks, the 9031 goes to the old community. The observed deletions on the old community are consistent with that.

Related but not the same: #5128 (Agents tab not community-scoped, actions land on wrong instance), #7184 / #7204 (managed-agent roster not scoped per community), #7798 (relay change re-mints identities, stranded unnamed keys), #4147 (agents bind to a different community than the owner).

Expected

  • Community Members (and the mutations behind it) are keyed by the active community's relay URL, and a community switch resets or invalidates them so no roster from another community is ever rendered or acted on.
  • A remove-member mutation binds the target relay at click time and refuses to publish if the active community changed since the roster was loaded.
  • Rows whose profile cannot be resolved on the active community should not be presented as plain "Unnamed member" with a destructive action available; at minimum show the npub and the community the row came from.

Impact

Owner-initiated, silent, cross-community deletion of relay membership. Recovery requires the owner to re-add every pubkey by hand (kind:9030) because there is no CLI path for relay membership and the roster for the damaged community is now empty in the UI.

Repro sketch

  1. Two communities A and B on one relay deployment. A has several relay members whose kind:0 exists only on A.
  2. In Desktop, open Settings → Community Members while A is active, then switch to B and open Community Members again quickly.
  3. Observe A's roster (as "Unnamed member" rows) rendered under B.
  4. Remove one row. Check A's relay_members: the row is gone.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions