block/buzz · error · IngestError::Rejected
missing e tag for target event
Error message
missing e tag for target event
What it means
Thrown by kind:9005 (DELETE_EVENT) when no e tag with a hex-decodable event id can be found. The extractor find_maps over tags for kind "e" whose content hex-decodes successfully — so both a missing e tag and an e tag whose value is not valid hex (wrong length, non-hex characters) produce this error.
Source
Thrown at crates/buzz-relay/src/handlers/side_effects.rs:648
9005 => {
// DELETE_EVENT: event author OR channel owner/admin.
if let Some(action_id) = extract_tag_value(event, "action_id") {
Uuid::parse_str(&action_id)
.map_err(|_| anyhow::anyhow!("invalid action_id tag"))?;
}
// Extract target event from e tag to check authorship.
let target_id = event
.tags
.iter()
.find_map(|tag| {
if tag.kind().to_string() == "e" {
tag.content().and_then(|v| hex::decode(v).ok())
} else {
None
}
})
.ok_or_else(|| anyhow::anyhow!("missing e tag for target event"))?;
// Verify the target event exists and belongs to the h-tag channel
// BEFORE storage. Fail closed: missing target → reject.
let target_event = state
.db
.get_event_by_id(tenant.community(), &target_id)
.await
.map_err(|e| anyhow::anyhow!("db error looking up target: {e}"))?
.ok_or_else(|| anyhow::anyhow!("target event not found"))?;
match target_event.channel_id {
Some(target_ch) if target_ch != channel_id => {
return Err(anyhow::anyhow!(
"target event belongs to a different channel"
));
}
None => {
return Err(anyhow::anyhow!("target event has no channel"));View on GitHub (pinned to f956e6fe06)
Solutions
- Include ["e", <64-char lowercase hex event id>] of the message you want deleted.
- Convert note1... identifiers to hex before publishing (bech32 decode).
- Verify the id length is exactly 64 hex characters with no 0x prefix or whitespace.
- Confirm you are deleting a message id, not a channel uuid or a user pubkey.
Example fix
// before — bech32 id or missing e tag
{ kind: 9005, tags: [["h", ch], ["action_id", uuid]] } // no e tag
{ kind: 9005, tags: [["h", ch], ["e", "note1qyq..."]] } // not hex
// after
{ kind: 9005, tags: [["h", ch], ["e", hexEventId]] } // hexEventId = 64 hex chars Defensive patterns
Strategy: validation
Validate before calling
const HEX64_RE = /^[0-9a-f]{64}$/;
function targetTag(eventId: string): string[] {
const hex = eventId.startsWith("note1") ? bech32ToHex(eventId) : eventId;
if (!HEX64_RE.test(hex)) throw new Error("Target event id must be 64 hex chars (convert note1... ids first)");
return ["e", hex];
} Type guard
function isHexEventId(v: string): boolean {
return /^[0-9a-fA-F]{64}$/.test(v.trim());
} Try / catch
try {
await sdk.publish(deleteEvent);
} catch (e) {
if (String(e).includes("missing e tag for target event")) {
throw new Error("No valid [\"e\", <64-char hex id\"] tag on the delete event");
} else throw e;
} Prevention
- Convert bech32 note1... ids to hex before building the e tag.
- Never put a channel uuid, npub, or a truncated id in the e tag.
- Assert the id is exactly 64 hex characters in your event builder.
When it happens
Trigger: Publishing kind:9005 without an e tag at all, or with [["e", "not-a-hex-id"], ["e", "zzzz"]] or an id with the 0x prefix. Only the first decodable e tag is used; an id that is a bech32 (note1...) encoding will also fail hex decoding.
Common situations: Passing a note1.../npub-style identifier where the relay expects 64-char hex; URL-decoding a deep link id incompletely; SDK field mix-ups sending the channel id in the e tag; truncation of the 64-char id by log/UI layers.
Related errors
- invalid action_id tag
- target event belongs to a different channel
- target event has no channel
- kind:9002 must include at least one metadata tag (name, abou
- invalid archived value: {v} (must be "true" or "false")
AI-assisted analysis of block/buzz@f956e6fe06 (2026-08-16).
Data as JSON: /api/errors/55010c8bee26573f.
Report an issue: GitHub.