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

  1. Verify the event's channel id exists in the community the event was published to
  2. Check relay DB connectivity if recently seeing transient failures — a DB error is being conflated with not-found
  3. Confirm the client is pointed at the correct relay/community (tenant scoping)
  4. 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

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


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)