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
- Format the a tag as "<kind>:<64-hex pubkey>:<d-value>", e.g. "30078:<pubkey>:general"
- Assert the coordinate has >= 2 colon-separated parts before publishing (a cheap client-side splitn(3,':') check)
- 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
- Never string-build coordinates by hand; use an SDK Coordinate type
- Check the template has all three placeholders before format!() — a missing segment drops the colon
- Round-trip parse the coordinate client-side with splitn(3, ':') exactly like the relay does
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
- missing e or a tag for target
- target event not found
- invalid pubkey in a-tag
- must be event author
- missing or invalid h tag
AI-assisted analysis of block/buzz@f956e6fe06 (2026-08-16).
Data as JSON: /api/errors/8404e70eafa32d58.
Report an issue: GitHub.