block/buzz · error · IngestError::Rejected
target event belongs to a different channel
Error message
target event belongs to a different channel
What it means
Thrown by kind:9005 when the target event exists but its stored channel_id differs from the channel in the delete event's h tag. Deletes are channel-scoped: the relay looks up the target, then requires target_event.channel_id == the h-tag channel before checking authorship. This prevents cross-channel deletion via forged h tags.
Source
Thrown at crates/buzz-relay/src/handlers/side_effects.rs:661
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"));
}
_ => {} // 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)View on GitHub (pinned to f956e6fe06)
Solutions
- Resolve the target message's actual channel first (query the event by id and read its h tag / channel), then publish the 9005 with THAT channel in the h tag.
- In UI flows, always pair the delete with the channel the message was rendered from, not a global channel id.
- Refresh cached channel ids (buzz channels list) if a channel was recreated.
Example fix
// before — deleting with the wrong h tag await sdk.deleteEvent(wrongChannelId, targetEventId); // target lives in otherChannelId // after — use the target's own channel const target = await buzz.messagesGet(targetEventId); await sdk.deleteEvent(target.channelId, targetEventId);
Defensive patterns
Strategy: validation
Validate before calling
// Resolve the target's own channel before publishing the delete
const target = await buzz.messagesGet(targetEventId);
const hTag = target.tags.find((t) => t[0] === "h");
if (!hTag) throw new Error("Target is not a channel message");
if (hTag[1] !== currentChannelId) {
currentChannelId = hTag[1]; // use the target's actual channel
}
await sdk.deleteEvent(currentChannelId, targetEventId); Type guard
function targetMatchesChannel(targetHTag: string | undefined, deleteHTag: string): boolean {
return targetHTag === deleteHTag;
} Try / catch
try {
await sdk.deleteEvent(channelId, eventId);
} catch (e) {
if (String(e).includes("belongs to a different channel")) {
const target = await buzz.messagesGet(eventId);
await sdk.deleteEvent(target.tags.find((t) => t[0] === "h")![1], eventId);
} else throw e;
} Prevention
- Always pair a delete with the channel the message was actually received in, not the currently-viewed channel.
- Refresh channel ids after channels are deleted/recreated — names persist, uuids change.
- In search/deep-link flows, carry the channel id alongside the event id.
When it happens
Trigger: Publishing kind:9005 with h tag of channel A but an e tag pointing at a message in channel B. Commonly happens with stale cached channel ids or when a message was moved/cross-posted and the client tracks the wrong channel.
Common situations: Deep links or search results yielding a message id that the client associates with the wrong channel; channel deleted and recreated with the same name but different uuid; copy-paste of e ids between channels in tooling.
Related errors
- invalid action_id tag
- missing e tag for target event
- 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/e93261ce4f94d938.
Report an issue: GitHub.