block/buzz · error · anyhow::Error

actor is not an active member

Error message

actor is not an active member

What it means

Buzz-relay rejects a kind 9022 (LEAVE_REQUEST) event when the signing actor's pubkey is not present in the target channel's member list. The handler loads members via db.get_members(community, channel_id) and requires an exact pubkey match; a miss means the actor never joined, already left, or was removed. The error aborts side-effect validation, so the event is rejected instead of stored.

Solutions

  1. Query the channel's member list (kinds [39002] scoped to the channel, or `buzz channels members`) and confirm the signer's pubkey is present before publishing the leave
  2. Double-check the h tag channel UUID matches the channel you actually belong to
  3. If membership was already revoked or a prior leave succeeded, no action is needed — the relay state is already what you asked for
  4. Treat this rejection as idempotent success in leave-all automation: catch it and move on instead of retrying

Example fix

// before: publish leave unconditionally
let event = EventBuilder::leave_request(channel_id, actor_keys);
relay.publish(event).await?; // rejected: actor is not an active member

// after: gate on active membership first
let members = client.query_members(channel_id).await?;
if members.iter().any(|m| m.pubkey == actor_pubkey) {
    relay.publish(EventBuilder::leave_request(channel_id, actor_keys)).await?;
}
Defensive patterns

Strategy: validation

Validate before calling

// Before publishing kind 9022, confirm active membership:
let members = client.query_members(channel_id).await?;
let is_member = members.iter().any(|m| m.pubkey == actor_pubkey.to_hex());
if !is_member {
    // skip: already removed / never joined — nothing to leave
    return Ok(());
}

Type guard

fn is_active_member(members: &[Member], actor: &str) -> bool {
    members.iter().any(|m| m.pubkey == actor)
}

Try / catch

// Treat as terminal, not retryable:
if err.to_string().contains("actor is not an active member") {
    // idempotent no-op for leave-all flows; do not re-send
}

Prevention

When it happens

Trigger: Publishing kind 9022 whose h tag points at a channel where the signer is not an active member: they were kicked by an owner, a previous leave already succeeded, or the h tag UUID is simply the wrong channel. Also hit when an agent harness replays a leave request after membership was revoked.

Common situations: Clients caching a stale channel list and leaving a channel the user was removed from; automation that iterates all known channels and leaves each one, including channels the signer never joined; test scripts signing with a fresh key that never went through join.

Related errors


AI-assisted analysis of block/buzz@eed74bde2f (2026-08-20). Data as JSON: /api/errors/5c57b2e7bce7e900. Report an issue: GitHub.

Appendix: source

Thrown at crates/buzz-relay/src/handlers/side_effects.rs:746

                    // Allow the owning human of any active owner-role agent in the
                    // channel, even when the human is not a channel member —
                    // diverges from kind:9001 intentionally.
                    if actor_owns_any_owner_agent(state, tenant.community(), &members, &actor_bytes)
                        .await?
                    {
                        return Ok(());
                    }
                    Err(anyhow::anyhow!("only owner can delete group"))
                }
            }
        }
        9022 => {
            // LEAVE_REQUEST: must be an active member, and cannot be the last owner.
            let members = state.db.get_members(tenant.community(), channel_id).await?;
            let actor_member = members.iter().find(|m| m.pubkey == actor_bytes);
            match actor_member {
                None => {
                    return Err(anyhow::anyhow!("actor is not an active member"));
                }
                Some(m) if m.role == "owner" => {
                    let owner_count = members.iter().filter(|m| m.role == "owner").count();
                    if owner_count <= 1 {
                        return Err(anyhow::anyhow!("cannot remove the last owner"));
                    }
                }
                _ => {}
            }
            Ok(())
        }
        _ => Ok(()),
    }
}

/// Emit a system message (kind 40099) signed by the relay keypair.
///
/// `idempotency_ts` is used as the event's `created_at`. Passing a stable

View on GitHub (pinned to eed74bde2f)