block/buzz · error · IngestError::Rejected

actor is not an active member

Error message

actor is not an active member

What it means

Thrown by the relay when processing a kind:9001 (REMOVE_USER) event whose p tag targets the actor's own key (self-leave), but the actor has no membership row in the channel. The relay loads the member list with get_members(community, channel_id) and fails when the actor's pubkey is absent. The event is rejected before storage, so the leave is a no-op.

Source

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

                    // NOTE: DB ENUM constraint prevents unknown values from being stored.
                    // If a new policy value is added to the ENUM, update this match.
                    _ => {}
                }
            }

            Ok(())
        }
        9001 => {
            // REMOVE_USER: self-remove allowed unless actor is the last owner; removing others requires owner/admin
            let target_pubkey =
                extract_p_tag(event).ok_or_else(|| anyhow::anyhow!("missing p tag"))?;
            if target_pubkey == actor_bytes {
                // Self-removal: 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(())
            } else {
                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 {
                    Some(m) if m.role == "owner" || m.role == "admin" => Ok(()),
                    Some(_) => {
                        if state
                            .db

View on GitHub (pinned to f956e6fe06)

Solutions

  1. Confirm you actually intend to leave — if you were already removed, treat this error as success (idempotent no-op) and drop the channel from local state.
  2. Verify the h tag channel id is current by listing your channels (buzz channels list) and re-publish with the correct uuid.
  3. If you were never a member and want out of the UI only, remove the channel locally instead of sending a 9001 event.
  4. If you believe you are a member, check kind:39002 membership state for that channel to see what the relay thinks before retrying.

Example fix

// before — leaving a channel you may not be in anymore
const ev = await sdk.removeUser(channelId, myPubkey); // kind:9001 p=self → rejected

// after — check membership first, then leave
const members = await buzz.channelsMembersList(channelId);
if (members.some((m) => m.pubkey === myPubkey)) {
  await sdk.removeUser(channelId, myPubkey);
} else {
  // already out — clear local channel state only
  await localStore.dropChannel(channelId);
}
Defensive patterns

Strategy: validation

Validate before calling

// Fetch the member list before attempting a self-leave (kind:9001 p=self)
const members = await buzz.channelsMembersList(channelId);
const isMember = members.some((m) => m.pubkey === myPubkey);
if (!isMember) {
  // treat as already-left: clear local state, do not publish
  await localStore.dropChannel(channelId);
}

Type guard

function isChannelMember(members: { pubkey: string }[], pubkey: string): boolean {
  return members.some((m) => m.pubkey.toLowerCase() === pubkey.toLowerCase());
}

Try / catch

try {
  await sdk.removeUser(channelId, myPubkey);
} catch (e) {
  if (String(e).includes("actor is not an active member")) {
    // Already out — converge local state instead of surfacing an error
    await localStore.dropChannel(channelId);
  } else throw e;
}

Prevention

When it happens

Trigger: Publishing kind:9001 with ["p", <your own pubkey>] to a channel you never joined, already left, or were removed from by an admin. Also fires when the h tag channel id is stale (channel recreated/deleted and you are sending an old channel uuid).

Common situations: Retrying a leave after being force-removed by an admin (double-leave); copying an old channel id from a deep link; agents re-using a cached channel uuid after the channel was deleted and recreated; test scripts that leave before joining.

Related errors


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