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
.dbView on GitHub (pinned to f956e6fe06)
Solutions
- 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.
- Verify the h tag channel id is current by listing your channels (buzz channels list) and re-publish with the correct uuid.
- If you were never a member and want out of the UI only, remove the channel locally instead of sending a 9001 event.
- 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
- Make leave operations idempotent in the client: treat 'not an active member' as success.
- Always resolve channel ids from a fresh channels list rather than cached deep links.
- In bulk-leave scripts, pre-fetch membership for all channels and skip non-members.
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
- not a member
- cannot remove the last owner
- actor not authorized for name/about/archived/visibility/ttl
- must be event author or channel owner/admin
- moderator access required
AI-assisted analysis of block/buzz@f956e6fe06 (2026-08-16).
Data as JSON: /api/errors/5c57b2e7bce7e900.
Report an issue: GitHub.