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

  1. Delete your own messages with the same key that authored them (for relay-signed messages, the key matching the p tag).
  2. For others' messages, use a key whose member role is owner or admin in that channel (check relay_members/get_members).
  3. 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.
  4. 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

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


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)