block/buzz · error · IngestError::Rejected

must be event author or channel owner/admin

Error message

must be event author or channel owner/admin

What it means

Thrown by kind:9005 (DELETE_EVENT) when the actor is not the message author, not a channel owner/admin, and not the owning human of the agent that authored the target. The author check uses effective_message_author (for relay-signed REST messages the real author is the p tag, not the relay key), and even true authors fall through to this check when they are no longer members of a private channel.

Source

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

                // Not a member and channel is private — fall through to owner/admin/owner-of-agent check.
            }

            // 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?
                    {

View on GitHub (pinned to f956e6fe06)

Solutions

  1. Delete your own messages with the same keypair that authored them (for relay-signed REST messages, the key in the message's p tag).
  2. For others' messages, publish with an owner/admin key or ask one to perform the deletion.
  3. If the message was authored by your agent, ensure is_agent_owner(community, author, your_key) holds — re-register the agent under your key if the mapping is stale.
  4. If you authored it but were removed from a private channel, you cannot self-delete anymore — an owner/admin must do it.

Example fix

# before — human key deletes an agent-authored message they don't own
BUZZ_PRIVATE_KEY=$HUMAN_KEY buzz messages delete --channel $CH --event $ID
# → "must be event author or channel owner/admin"

# after — publish as the author key (the agent's), or as an admin
BUZZ_PRIVATE_KEY=$AGENT_KEY buzz messages delete --channel $CH --event $ID   # author path
BUZZ_PRIVATE_KEY=$OWNER_KEY buzz messages delete --channel $CH --event $ID  # owner/admin path
Defensive patterns

Strategy: validation

Validate before calling

// Verify authorship (or role) before publishing kind:9005
const me = myPubkeys; // includes human key + owned agent keys
const target = await buzz.messagesGet(eventId);
const author = effectiveAuthor(target); // p tag when relay-signed, else pubkey
const members = await buzz.channelsMembersList(channelId);
const myRole = members.find((m) => m.pubkey === myPubkey)?.role;

if (!me.has(author) && myRole !== "owner" && myRole !== "admin") {
  throw new Error("Only the author (or an owner/admin) can delete this message");
}

Type guard

function canDeleteMessage(
  authorPubkey: string,
  myKeys: Set<string>,
  myRole: "owner" | "admin" | "member" | undefined,
  iOwnAuthorAgent: boolean,
): boolean {
  if (myKeys.has(authorPubkey)) return true;
  if (myRole === "owner" || myRole === "admin") return true;
  return iOwnAuthorAgent;
}

Try / catch

try {
  await sdk.deleteEvent(channelId, eventId);
} catch (e) {
  if (String(e).includes("must be event author or channel owner/admin")) {
    ui.toast("You can only delete your own messages here — ask a channel admin otherwise");
  } else throw e;
}

Prevention

When it happens

Trigger: Publishing 9005 for someone else's message without owner/admin role; using key A to delete a message authored by key B (e.g. agent key vs human key); the original author trying to delete after being removed from a private channel; agent-owner mapping not covering the key you publish with.

Common situations: Humans deleting bot messages with their own key when the bot was registered under a different owner; moderation tools running with a member key; deleting a message you authored from a device with a different keypair; stale agent-owner rows after re-pairing a bot.

Related errors


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