block/buzz · error · anyhow::Error
must be event author or channel owner/admin
Error message
must be event author or channel owner/admin
What it means
Authorization for a kind:9005 delete of someone else's message: the actor must be a channel owner/admin member, the effective author of the target (the p-tag pubkey for relay-signed REST messages), or the registered owning human of the agent that authored the message (is_agent_owner). Failing all four checks, the relay rejects the delete. The effective-author rule exists because REST-published messages are signed by the relay key, with the real author in a p tag.
Solutions
- Delete your own messages with the same key that authored them (for relay-signed messages, the key matching the p tag).
- For others' messages, use a key whose member role is owner or admin in that channel (check relay_members/get_members).
- For deleting an agent's message as its owner, ensure the agent-ownership relation exists (is_agent_owner lookup) — register it if the agent was set up outside the normal flow.
- If acting for moderation legitimately, have an admin grant the acting key the admin role first.
Defensive patterns
Strategy: validation
Validate before calling
// before publishing 9005 for someone else's message, check your standing
const members = await relay.channelMembers(channelId);
const me = members.find((m) => m.pubkey === myPubkey);
const canModerate = me && (me.role === 'owner' || me.role === 'admin');
const isAuthor = effectiveAuthor(target) === myPubkey;
if (!canModerate && !isAuthor) throw new Error('not permitted to delete this message'); Prevention
- Sign each user's deletes with that user's key — never one shared moderation key with member role.
- For relay-signed REST messages, ensure the p tag carries the real author so the self-delete path matches.
- Register agent ownership at agent-provisioning time so humans can manage their agents' messages.
When it happens
Trigger: A plain member publishing 9005 against another member's message; a human owner deleting their agent's message without the agent-ownership relation being registered; acting with a different key than the one that authored the message; relay-signed messages whose p tag does not match the actor.
Common situations: Moderation tooling running with a member-level key; humans expecting implicit ownership of their agents' messages without the DB ownership link; key rotation making the actor no longer the recorded author.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- target event belongs to a different channel
- actor is not an active member
- only owner can delete group
- target event has no channel
- target event not found
AI-assisted analysis of block/buzz@dad5a33865 (2026-08-20).
Data as JSON: /api/errors/e9ba5e28c70d961f.
Report an issue: GitHub.
Appendix: source
Thrown at crates/buzz-relay/src/handlers/side_effects.rs:651
}
// Not the author, or author who is no longer a member of a private channel —
// must be owner/admin or the owning human of the message's agent-author.
let members = state.db.get_members(tenant.community(), channel_id).await?;
if actor_is_channel_owner_or_admin(&members, &actor_bytes) {
Ok(())
} else {
// Allow the owning human of the agent that authored the target message,
// even when the human is not a channel member.
if state
.db
.is_agent_owner(tenant.community(), &author, &actor_bytes)
.await?
{
Ok(())
} else {
Err(anyhow::anyhow!(
"must be event author or channel owner/admin"
))
}
}
}
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(());View on GitHub (pinned to dad5a33865)