block/buzz · error · IngestError::Rejected

missing e or a tag for target

Error message

missing e or a tag for target

What it means

validate_standard_deletion_event() rejects a NIP-09 kind:5 deletion event that names no target: has_e_tag() found no 'e' tag at all, and no 'a' tag with content could be located. Buzz treats e/a as the two target classes for deletions — e tags point at concrete event ids, a tags at addressable (kind 30000-39999) coordinates. A deletion with neither is ambiguous and is refused before storage.

Source

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

/// Buzz accepts standard deletions for self-authored events, plus the owning
/// human deleting their agent's events (mirrors `validate_edit_ownership`).
/// Channel admin deletions continue to use kind 9005.
pub async fn validate_standard_deletion_event(
    tenant: &TenantContext,
    event: &Event,
    state: &Arc<AppState>,
) -> anyhow::Result<()> {
    let actor_bytes = effective_message_author(event, &state.relay_keypair.public_key());
    let target_ids = extract_target_event_ids(event);

    if !has_e_tag(event) {
        // a-tag deletion: verify author owns the addressable event
        let a_tag = event
            .tags
            .iter()
            .find(|t| t.kind().to_string() == "a")
            .and_then(|t| t.content().map(|s| s.to_string()))
            .ok_or_else(|| anyhow::anyhow!("missing e or a tag for target"))?;
        let parts: Vec<&str> = a_tag.splitn(3, ':').collect();
        if parts.len() < 2 {
            return Err(anyhow::anyhow!("invalid a-tag format"));
        }
        let target_pubkey_bytes =
            hex::decode(parts[1]).map_err(|_| anyhow::anyhow!("invalid pubkey in a-tag"))?;
        if target_pubkey_bytes != actor_bytes
            && !state
                .db
                .is_agent_owner(tenant.community(), &target_pubkey_bytes, &actor_bytes)
                .await?
        {
            return Err(anyhow::anyhow!("must be event author"));
        }
        return Ok(());
    }

    for target_id in target_ids {

View on GitHub (pinned to f956e6fe06)

Solutions

  1. Add at least one ["e", "<64-hex event id>"] tag naming the event(s) to delete
  2. For addressable events, use ["a", "<kind>:<pubkey-hex>:<d-identifier>"] instead
  3. Log event.tags before publish and assert the array contains an e or a entry

Example fix

// before
EventBuilder::new(Kind::EventDeletion, "", []).to_event(&keys) // no targets

// after
EventBuilder::new(
    Kind::EventDeletion,
    "",
    [Tag::event(EventId::from_hex("<target-id>")?)],
).to_event(&keys)
Defensive patterns

Strategy: validation

Validate before calling

// Client-side pre-publish check mirroring has_e_tag / a-tag lookup
fn has_deletion_target(tags: &[Tag]) -> bool {
    tags.iter().any(|t| t.kind().to_string() == "e")
        || tags.iter().any(|t| t.kind().to_string() == "a" && t.content().is_some())
}
assert!(has_deletion_target(&event.tags), "deletion needs an e or a target tag");

Type guard

fn isDeletionTargeted(tags: string[][]): boolean {
  return tags.some(t => t[0] === "e") || tags.some(t => t[0] === "a" && !!t[1]);
}

Try / catch

match validate_standard_deletion_event(&tenant, &event, &state).await {
    Ok(()) => store_and_apply(),
    Err(e) if e.to_string().contains("missing e or a tag") => reject_with("deletion must name an e or a target", &event.id),
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: Publishing kind:5 with an empty or missing tags list; building the deletion with only an author/metadata tag (e.g. just a 'reason' tag or a malformed tag array); a client bug that serializes tags under the wrong key so 'e'/'a' never reach the relay.

Common situations: Hand-rolled event JSON where the tags array is omitted or misspelled; SDK version change that renamed the tags field; test fixtures copied from a text post instead of a deletion template.

Related errors


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