block/buzz · error · anyhow::Error
channel not found
Error message
channel not found
What it means
validate_admin_event looks up the target channel via get_channel_for_event_write before applying admin mutations (archive/unarchive etc.). Any DB error or missing channel is collapsed into 'channel not found', which rejects the admin event. Note it also covers archived-channel gating: after lookup, archived channels reject mutations except kind:9002 unarchive.
Solutions
- Verify the event's channel id exists in the community the event was published to
- Check relay DB connectivity if recently seeing transient failures — a DB error is being conflated with not-found
- Confirm the client is pointed at the correct relay/community (tenant scoping)
- Consider distinguishing DB errors from missing rows in the map_err for accurate surfacing
Example fix
// before
.map_err(|_| anyhow::anyhow!("channel not found"))?;
// after
state.db.get_channel_for_event_write(tenant.community(), channel_id).await
.map_err(|e| match e {
buzz_db::Error::NotFound => anyhow::anyhow!("channel not found"),
other => anyhow::anyhow!("channel lookup failed: {other}"),
})?; Defensive patterns
Strategy: validation
Validate before calling
// caller-side precheck via relay query before publishing an admin event
let exists = state.db.get_channel_for_event_write(tenant.community(), channel_id).await.is_ok();
if !exists { return Err("target channel does not exist in this community"); } Type guard
null
Try / catch
match validate_admin_event(&state, &tenant, &event).await {
Err(e) if e.to_string().contains("channel not found") => {
// reject event with NACK; check channel id / tenant scoping
}
other => other?,
} Prevention
- Scope channel ids to the community (h tag) and verify before publishing admin events
- Confirm client and relay target the same tenant/community
- Distinguish DB errors from missing rows so transient outages are not mislabeled
When it happens
Trigger: Kind 9xxx admin event referencing a channel_id (h tag / d tag) that does not exist in the tenant, or the DB read fails (connection error, transient failure) — both mapped to the same 'channel not found' error.
Common situations: Client sends an admin event for a channel that was deleted; cross-tenant/community id mismatch so the lookup finds nothing; Postgres briefly unavailable making the read error out (misreported as not-found).
Related errors
- failed to build setup nudge
- invalid role
- missing p tag
- moderation notice recipient must be a 32-byte pubkey, got
- system message insert failed
AI-assisted analysis of block/buzz@dad5a33865 (2026-09-05).
Data as JSON: /api/errors/0a871b4d14f08b94.
Report an issue: GitHub.
Appendix: source
Thrown at crates/buzz-relay/src/handlers/side_effects.rs:336
) -> anyhow::Result<()> {
// CREATE_GROUP doesn't need an existing channel — skip h-tag extraction
if kind == 9007 {
return Ok(());
}
// Extract channel from h tag
let channel_id =
extract_h_tag_channel(event).ok_or_else(|| anyhow::anyhow!("missing or invalid h tag"))?;
let actor_bytes = event.pubkey.to_bytes().to_vec();
// Reject mutations on archived channels — except kind:9002 with archived=false
// (unarchive), which must be allowed through so the channel can be restored.
let channel = state
.db
.get_channel_for_event_write(tenant.community(), channel_id)
.await
.map_err(|_| anyhow::anyhow!("channel not found"))?;
let is_unarchive_request = kind == 9002
&& event.tags.iter().any(|t| {
let parts = t.as_slice();
parts.len() >= 2 && parts[0] == "archived" && parts[1] == "false"
});
if channel.archived_at.is_some() && !is_unarchive_request {
return Err(anyhow::anyhow!("channel is archived"));
}
match kind {
9000 => {
// An absent role tag means "no role change requested": for an existing
// member that preserves the role they already hold, and only defaults
// to Member for a genuinely new member. Defaulting unconditionally to
// Member made a bare self-targeted PUT_USER silently demote an owner.
let role_str = extract_tag_value(event, "role");
let requested_role = match role_str {
Some(ref s) => match s.parse::<buzz_db::channel::MemberRole>() {View on GitHub (pinned to dad5a33865)