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)
- 13:40:59 — a scripted NIP-42 read as one of the agents succeeds; kind:39002 rosters for several channels are intact.
- ~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.
- 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.
- 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
- Two communities A and B on one relay deployment. A has several relay members whose kind:0 exists only on A.
- In Desktop, open Settings → Community Members while A is active, then switch to B and open Community Members again quickly.
- Observe A's roster (as "Unnamed member" rows) rendered under B.
- Remove one row. Check A's
relay_members: the row is gone.
Summary
Removing "Unnamed member" rows from Settings → Community Members right after switching communities deleted relay membership (
relay_membersrows) on the previous community, not the one shown in the sidebar. Every headlessbuzz-acpagent on that relay then failed NIP-42 auth withrestricted: not a relay memberand the systemd fleet went into a restart loop.Environment
0.2.1, one deployment serving four communities by Host header (buzz.,bros.,giles.,andy.<domain>), Cloudflare-fronted,auth_required: true,restricted_writes: truebuzz-acpagents on a Linux host (systemdbuzz-agent@<name>.service), relay members of thebuzz.community only, no NIP-OA auth tagWhat happened (timeline, UTC, 2026-09-22)
buzz.community to another community on the same deployment, opens Community Members, sees a list of "Unnamed member" rows, and removes them.buzz-acprestart 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.buzz.community are untouched; only relay membership was removed.Why I think this is a Desktop bug, not a relay bug
handle_relay_admin_eventresolves the tenant from the Host header andbuzz_db::store::relay_members::remove_relay_memberrunsDELETE 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 thebuzz.community's connection.desktop/src/features/community-members/hooks.ts→export const relayMembersQueryKey = ["relayMembers"] as const;.useCommunityInitcallsrelayClient.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.useUsersBatchQueryagainst the active community. Pubkeys that only have a kind:0 on the previous community therefore render asUnnamed 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.useRemoveRelayMemberMutationoptimistically edits the same unscoped cache and publishes kind:9031 through the singletonrelayClient/ Tauri workspace override. If either the relay client session or the Taurirelay_url_overridehas 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
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
relay_members: the row is gone.