Skip to content

Self-join (kind:9021) hard-codes role=member, making a self-joined agent permanently unmentionable #6561

Open

Description

@rmichelena

Summary

handle_join_request (kind:9021) hard-codes MemberRole::Member. An agent that joins a channel itself is therefore recorded as a human member — and because clients use the channel role as the "is this an agent" signal, that agent becomes invisible to @mention autocomplete, with no in-app way to repair it.

The same channel reached through a different entry point works fine, which is what makes this hard to spot.

Three entry paths, three different role decisions

Entry path How the role is decided
Desktop invite role: user.isAgent ? "bot" : "member" — desktop/src/features/channels/ui/MembersSidebar.tsx
kind:9000 / add-member handle_put_user reads a role tag; falls back to Member only for a brand-new member
kind:9021 / self-join hard-coded MemberRole::Member; the joiner cannot express a role

kind:9021 carries no role tag and buzz channels join exposes no --role, so this is not a client omission — there is no way to ask for anything else.

Why Member is the wrong default here specifically

MemberRole's own documentation says Bot is not a lower rung:

// crates/buzz-core/src/channel.rs
/// The hierarchy for permission checks is: Owner > Admin > Member > Guest.
/// Bot is a **separate designation** — it is not part of the linear hierarchy.

So assigning Member to an agent is not a conservative privilege choice — it is a category error, and it is the category clients read.

Impact

  1. Agent self-joins an open channel (the only channels where 9021 is accepted).
  2. It lands as member.
  3. Desktop's mention eligibility requires isAgent === true || member.role === "bot", so it never appears in @ autocomplete.
  4. Repair requires role: bot on a kind:9000, which the relay restricts to owner/admin (only owners/admins may change an active member's role) — and the Desktop role menu offers only admin/member/guest, and hides itself entirely for members already marked bot. There is no in-app path back.

Observed on a self-hosted relay: an agent self-joined at T, was unmentionable for the entire session, and only recovered after an owner-signed add-member --role bot.

Suggested direction

The relay already defines an agent discriminator and relies on it elsewhere:

// crates/buzz-db/src/usage.rs
/// Agent discriminator: `agent_owner_pubkey IS NOT NULL`.

Using that same discriminator in handle_join_request fixes the self-join path without touching handle_put_user, so explicit admin/member/guest assignment for humans is unaffected. PR to follow.

Alternatives, if maintainers prefer a different shape: accept an optional role tag on kind:9021, or have clients repair the role after joining. Both seem worse — the first lets a joiner self-assign, the second spreads the decision across clients that already disagree.

Note on closed relays

agent_owner_pubkey is exactly the field that stays NULL on relays with require_relay_membership = true (#5581). Where that is unresolved, the discriminator reports every agent as a human and this fix is a no-op — so the two are the same knot, and #5581 is a prerequisite for closed deployments rather than a separate concern.

Activity

  1. rmichelena commented on Nov 9, 2026

    @rmichelena
    Author

    A fourth entry path with the same outcome: adding an agent from the mobile app records it as member.

    Entry path How the role is decided
    Mobile "Add members" addMembers is called without a role (add_members_sheet.dart:186), and the role defaults to 'member' (channel_management_actions.dart:172)

    The picker in that sheet already knows the candidate is an agent (isAgent = member.isBot || ownerPubkey != null, mention_candidates.dart:102), and Desktop's equivalent passes role: user.isAgent ? "bot" : "member" (MembersSidebar.tsx:596).

    Observed on our relay today (2026-11-09). In the same channel, four agents were added within 21 minutes:

    • Three were added from macOS Desktop. Their kind:9000 carries role bot, and all three eventually appeared in the macOS mention picker. One of them showed up only after a delay we have not explained yet.
    • One was added from iOS. Its kind:9000 carries role member, and it never appears in the macOS picker, across two app restarts.
    • iOS shows all four, because mobile accepts NIP-OA ownership as agent evidence and Desktop does not.

    The receiving side is the same as in this issue: Desktop only seeds relay-agent candidates from bot-role membership unless the agent has an owner-authored kind:30177 (relay_directory.rs:156, agent_directory.rs:225). An agent recorded as member or owner is still classified as an agent elsewhere, so it is hidden rather than offered as a person. I'll open a small mobile PR that passes bot for agent candidates. The broader fix is for Desktop to accept the same NIP-OA evidence that mobile already uses, which would cover self-join (this issue), mobile adds, and agent-created channels (#7161) together.

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