block/buzz · error · IngestError::Rejected

invalid a-tag format

Error message

invalid a-tag format

What it means

The kind:5 deletion carried an 'a' tag (and no 'e' tag), but splitting its value on ':' with splitn(3, ':') produced fewer than 2 segments — i.e. the coordinate is missing the kind:pubkey separator. A NIP-01 'a' coordinate must be "<kind>:<pubkey-hex>:<d-identifier>", and the relay needs at least kind and pubkey segments to authorize the deletion.

Source

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

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 {
        let target_event = state
            .db
            .get_event_by_id_including_deleted(tenant.community(), &target_id)

View on GitHub (pinned to f956e6fe06)

Solutions

  1. Format the a tag as "<kind>:<64-hex pubkey>:<d-value>", e.g. "30078:<pubkey>:general"
  2. Assert the coordinate has >= 2 colon-separated parts before publishing (a cheap client-side splitn(3,':') check)
  3. If you meant to delete a concrete event rather than an addressable one, use an e tag with the 32-byte event id instead

Example fix

// before
Tag::custom(TagKind::Custom("a"), vec![format!("{}", kind)]) // "30078" → invalid a-tag format

// after
Tag::custom(
    TagKind::Custom("a"),
    vec![format!("{}:{}:{}", kind, pubkey_hex, d_identifier)],
)
Defensive patterns

Strategy: validation

Validate before calling

// Validate the coordinate shape before publishing
fn valid_a_tag(value: &str) -> bool {
    let parts: Vec<&str> = value.splitn(3, ':').collect();
    parts.len() >= 2 && !parts[0].is_empty() && !parts[1].is_empty()
}
let a = format!("{}:{}:{}", kind, pubkey_hex, d);
assert!(valid_a_tag(&a));

Type guard

function isValidCoordinate(v: string): boolean {
  const parts = v.split(":");
  return parts.length >= 2 && parts[0] !== "" && /^[0-9a-f]{64}$/.test(parts[1]);
}

Try / catch

if let Err(e) = validate_standard_deletion_event(&tenant, &event, &state).await {
    if e.to_string().contains("invalid a-tag format") {
        return reject("a tag must be kind:pubkey:d-identifier", &event.id);
    }
    return Err(e);
}

Prevention

When it happens

Trigger: a tag value like "30078" (kind only, no colon), "" (empty string), or "chat/topic" (non-coordinate content); a client concatenating the coordinate with a separator other than ':'; template string interpolation that drops the pubkey segment.

Common situations: Building the a tag from a struct with a buggy Display impl; copying a d-tag identifier into the a tag position; locale-specific separators introduced by string tooling.

Related errors


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