block/buzz · error · IngestError::Rejected

actor not authorized for name/about/archived/visibility/ttl

Error message

actor not authorized for name/about/archived/visibility/ttl changes

What it means

Thrown by kind:9002 (EDIT_METADATA) when the event includes any privileged tag (name, about, archived, visibility, ttl) but the actor is neither an owner/admin member nor the owning human of an active owner-role agent in the channel. Note the fallback here differs from 9001: a non-member who owns an owner-role agent IS allowed.

Source

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

                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(()),
                    _ => {
                        // 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!(
                            "actor not authorized for name/about/archived/visibility/ttl changes"
                        ))
                    }
                }
            } else {
                // topic/purpose: any member
                let is_member = state
                    .is_member_cached(tenant.community(), channel_id, &actor_bytes)
                    .await?;
                if is_member {
                    Ok(())
                } else {
                    Err(anyhow::anyhow!("not a member"))
                }
            }
        }
        9005 => {
            // DELETE_EVENT: event author OR channel owner/admin.

View on GitHub (pinned to f956e6fe06)

Solutions

  1. Restrict your edit to ["topic"] / ["purpose"] tags — those only require membership.
  2. Get your key promoted to admin/owner, or publish with the owner/admin keypair.
  3. If you own an owner-role agent, verify it is active in the channel and the agent-owner mapping matches the key you are publishing with, then retry.
  4. Split the edit: keep the privileged fields for the owner and send only what your role permits.

Example fix

# before — plain member renames the channel
buzz channels update --channel $CH --name "new-name"  # → actor not authorized for name/about/... changes

# after — member edits only topic; owner does the rename
buzz channels update --channel $CH --topic "New topic"            # member: OK
BUZZ_PRIVATE_KEY=$OWNER_KEY buzz channels update --channel $CH --name "new-name"
Defensive patterns

Strategy: validation

Validate before calling

const PRIVILEGED = ["name", "about", "archived", "visibility", "ttl"];

// Gate the edit UI/payload on role before publishing kind:9002
const members = await buzz.channelsMembersList(channelId);
const me = members.find((m) => m.pubkey === myPubkey);
const privileged = me?.role === "owner" || me?.role === "admin";
const fields = privileged ? draft : pick(draft, ["topic", "purpose"]);
if (Object.keys(fields).some((k) => PRIVILEGED.includes(k)) && !privileged) {
  throw new Error("Only owners/admins can change name/about/archived/visibility/ttl");
}

Type guard

function isPrivilegedMetadataKey(k: string): boolean {
  return ["name", "about", "archived", "visibility", "ttl"].includes(k);
}

Try / catch

try {
  await sdk.publish(editEvent);
} catch (e) {
  if (String(e).includes("not authorized for name/about/archived/visibility/ttl")) {
    // Downgrade the edit to topic/purpose only, or request an owner
    await sdk.editChannelMetadata(channelId, pick(draft, ["topic", "purpose"]));
  } else throw e;
}

Prevention

When it happens

Trigger: A plain member (or non-member) publishes 9002 containing e.g. ["name", ...] or ["visibility", ...]. They are not owner/admin in the member list and actor_owns_any_owner_agent(...) finds no owner-role agent owned by them.

Common situations: Members assuming topic-style permissions extend to renames; automation running with a member keypair; humans who own a regular (non-owner-role) agent trying channel renames through it; agent-owner rows missing after re-registration of the agent under a different key.

Related errors


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