block / block/buzz

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

Open
#6,561 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
32.7k
Forks
4.3k
Avg merge
1d 13h
Merged PRs (30d)
253

Description

## 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:

```rust
// 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:

```rust
// 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.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.