block/buzz · error · IngestError::Rejected
only owner can delete group
Error message
only owner can delete group
What it means
Thrown by the relay's side-effect authorization for kind:9008 (DELETE_GROUP) events. The actor's pubkey must appear in the channel's active member list with role == "owner", or the actor must be the owning human of an active owner-role agent in that channel (an intentional divergence from kind:9001, per the comment in the source). If neither holds, the delete is rejected with this message.
Source
Thrown at crates/buzz-relay/src/handlers/side_effects.rs:732
}
}
}
9008 => {
// DELETE_GROUP: owner only, or the owning human of the channel's agent-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 {
Some(m) if m.role == "owner" => Ok(()),
_ => {
// 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"));
}
}
_ => {}View on GitHub (pinned to f956e6fe06)
Solutions
- Re-publish the kind:9008 event signed by a pubkey whose channel membership role is owner
- If acting through an agent, promote that agent to owner role and confirm it is active, then retry as the agent's owning human
- Promote the current actor to owner first (membership/role change from an existing owner), then delete
- Verify the signing key and community host match the owner membership row (check the channel's kind:39002 membership events)
Example fix
// before: signed by an admin key — rejected with "only owner can delete group" let ev = EventBuilder::new(Kind::Custom(9008), "").tags([tag_h(channel_id)]).to_event(&admin_keys)?; // after: sign with an owner-role member's key let members = client.get_members(channel_id).await?; let owner = members.iter().find(|m| m.role == "owner").ok_or(NoOwner)?; let ev = EventBuilder::new(Kind::Custom(9008), "").tags([tag_h(channel_id)]).to_event(&owner_keys)?;
Defensive patterns
Strategy: validation
Validate before calling
// Before publishing kind:9008, confirm the signer is an active owner of the channel
// (membership resolves from kind:39002 events whose d-tag is the channel id).
let members = client.get_members(channel_id).await?;
let is_owner = members.iter().any(|m| m.pubkey == actor_pubkey && m.role == "owner");
if !is_owner {
return Err(DeleteGroupError::NotOwner);
} Type guard
fn is_owner(m: &ChannelMember) -> bool {
m.role == "owner"
} Try / catch
match publish_delete_group(&owner_keys, channel_id).await {
Ok(_) => {}
Err(e) if e.to_string().contains("only owner can delete group") => {
// terminal authorization failure — do not retry; surface to the user
}
Err(e) => return Err(e),
} Prevention
- Gate delete-group UI actions on the actor's owner role fetched from current membership before offering the action
- Keep agent ownership metadata (owning human + agent role) current so the owning-human fallback resolves
- Log actor pubkey and channel id on rejection to distinguish wrong-key from wrong-role
- Remember admin role is not delete rights — only 9008 owner (or owner-agent's human) may delete
When it happens
Trigger: Publishing a signed kind:9008 event where the signer's membership role is "member" or "admin" (not "owner"), or the signer is not a channel member at all, AND actor_owns_any_owner_agent() finds no active owner-role agent in the channel owned by the actor.
Common situations: An admin assumes delete rights (admin suffices for kind:9001 edits, not 9008 deletes); a human tries to delete via their agent after the agent was demoted or deactivated; signing with a different key than the one holding the owner membership; the actor's membership lives under a different community host (tenant scoping hides it).
Related errors
- moderator access required
- must be event author
- actor not authorized
- only owners/admins may grant elevated roles
- only owners/admins may change an active member's role
AI-assisted analysis of block/buzz@f956e6fe06 (2026-08-16).
Data as JSON: /api/errors/485987abc4317860.
Report an issue: GitHub.