block/buzz · error · IngestError::Rejected
target event has no channel
Error message
target event has no channel
What it means
Thrown by kind:9005 when the target event exists but has channel_id = NULL in the database — it is not a channel message. The delete-by-event path only operates on channel-scoped events; direct messages, DM wrappers, and other non-channel events cannot be deleted this way.
Source
Thrown at crates/buzz-relay/src/handlers/side_effects.rs:666
.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"));
}
_ => {} // Same channel — OK
}
// Check if actor is the event author.
// For relay-signed REST messages, the real author is in the p tag.
let author =
effective_message_author(&target_event.event, &state.relay_keypair.public_key());
if author_delete_can_use_self_delete_path(&author, &actor_bytes, event) {
// Author deleting their own message: re-gate on membership/open visibility so that
// a removed private-channel member cannot mutate old messages after access is revoked.
let is_member = state
.is_member_cached(tenant.community(), channel_id, &actor_bytes)
.await?;
if is_member {
return Ok(());
}
let is_open = stateView on GitHub (pinned to f956e6fe06)
Solutions
- Only send kind:9005 for events that actually belong to a channel (they were received via a channel subscription / have an h tag).
- For DM deletion, use the DM-specific mechanism (kind:5 deletion event referencing the target e tag) rather than 9005.
- Filter candidate ids client-side: skip events whose channel association is unknown.
- If you expected the event to be in a channel, verify how it was published — it may have been stored outside channel scope by design.
Example fix
// before — universal delete path feeds DM ids into 9005
await sdk.deleteEvent(currentChannelId, selectedEventId); // selectedEventId is a DM → "target event has no channel"
// after — branch on event scope
if (event.tags.some((t) => t[0] === "h")) {
await sdk.deleteEvent(channelId, event.id); // channel message → 9005
} else {
await sdk.publishDeletion(event.id); // kind:5 generic deletion
} Defensive patterns
Strategy: type-guard
Validate before calling
// Only route channel-scoped events into the kind:9005 path
const target = await buzz.messagesGet(eventId);
if (!target.tags.some((t) => t[0] === "h")) {
// DM or non-channel event — use the generic deletion kind instead
await sdk.publishDeletion(eventId);
} else {
await sdk.deleteEvent(channelId, eventId);
} Type guard
function isChannelEvent(tags: string[][]): boolean {
return tags.some((t) => t[0] === "h" && typeof t[1] === "string" && t[1].length > 0);
} Try / catch
try {
await sdk.deleteEvent(channelId, eventId);
} catch (e) {
if (String(e).includes("target event has no channel")) {
await sdk.publishDeletion(eventId); // fall back to generic kind:5 deletion
} else throw e;
} Prevention
- Branch delete flows on event scope: channel messages via 9005, DMs via their own deletion mechanism.
- Do not feed ids from unified inboxes (which mix DMs and channel messages) into the channel delete path.
- When publishing events you may later delete, keep the h tag so they stay channel-scoped.
When it happens
Trigger: Pointing a kind:9005 e tag at a kind:14 DM/gift-wrap, a user-metadata event, or any event stored without a channel association. The h-tag scoping check finds None and rejects.
Common situations: Generic 'delete any message' UI actions that feed whatever event id is selected into the channel delete path; scripts iterating an inbox containing DMs; deletes targeting relay-signed REST artifacts recorded without channel scope.
Related errors
- invalid action_id tag
- missing e tag for target event
- target event belongs to a different 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/33ff9f58bd442161.
Report an issue: GitHub.