{"record":{"id":"0a871b4d14f08b94","repo":"block/buzz","slug":"channel-not-found","errorCode":null,"errorMessage":"channel not found","messagePattern":"channel not found","errorType":"validation","errorClass":"IngestError::Rejected","httpStatus":null,"severity":"error","filePath":"crates/buzz-relay/src/handlers/side_effects.rs","lineNumber":328,"sourceCode":") -> anyhow::Result<()> {\n    // CREATE_GROUP doesn't need an existing channel — skip h-tag extraction\n    if kind == 9007 {\n        return Ok(());\n    }\n\n    // Extract channel from h tag\n    let channel_id =\n        extract_h_tag_channel(event).ok_or_else(|| anyhow::anyhow!(\"missing or invalid h tag\"))?;\n\n    let actor_bytes = event.pubkey.to_bytes().to_vec();\n\n    // Reject mutations on archived channels — except kind:9002 with archived=false\n    // (unarchive), which must be allowed through so the channel can be restored.\n    let channel = state\n        .db\n        .get_channel(tenant.community(), channel_id)\n        .await\n        .map_err(|_| anyhow::anyhow!(\"channel not found\"))?;\n    let is_unarchive_request = kind == 9002\n        && event.tags.iter().any(|t| {\n            let parts = t.as_slice();\n            parts.len() >= 2 && parts[0] == \"archived\" && parts[1] == \"false\"\n        });\n    if channel.archived_at.is_some() && !is_unarchive_request {\n        return Err(anyhow::anyhow!(\"channel is archived\"));\n    }\n\n    match kind {\n        9000 => {\n            // An absent role tag means \"no role change requested\": for an existing\n            // member that preserves the role they already hold, and only defaults\n            // to Member for a genuinely new member. Defaulting unconditionally to\n            // Member made a bare self-targeted PUT_USER silently demote an owner.\n            let role_str = extract_tag_value(event, \"role\");\n            let requested_role = match role_str {\n                Some(ref s) => match s.parse::<buzz_db::channel::MemberRole>() {","sourceCodeStart":310,"sourceCodeEnd":346,"githubUrl":"https://github.com/block/buzz/blob/f956e6fe06a76e50cbd8fba1a162482e752e7f1a/crates/buzz-relay/src/handlers/side_effects.rs#L310-L346","documentation":"get_channel() for the h-tag UUID failed and the error is mapped to \"channel not found\" — the UUID does not resolve to a channel in this community (and note the map_err also folds genuine DB failures into this message). Admin-event validation happens before storage, so the mutation is rejected up front. Cross-community UUIDs never resolve because the lookup is fenced to tenant.community().","triggerScenarios":"h tag carries a UUID from a different community/relay or a stale/deleted channel; UUID typo (swapped digits still parse but match nothing); the channel was hard-deleted after a kind 9008 delete; the database is unreachable and the DB error is masked as not-found.","commonSituations":"Copied channel UUID from another environment (staging → prod); client cached an old channel list; concurrent delete racing a later admin event; using a random/generated UUID in tests without creating the channel first.","solutions":["Re-list channels for the community and copy the exact UUID into the h tag","If the channel was deleted, recreate it (kind 9007) before issuing further admin events","Check relay logs/DB health — a real database error is reported through this same message, so verify connectivity before assuming the UUID is wrong","In tests, create the channel first and use the returned id, never a fabricated UUID"],"exampleFix":"// before: fabricated/stale UUID\nlet h = \"00000000-0000-0000-0000-000000000000\".to_string();\n\n// after: resolve the real channel id\nlet channels = client.get_channels().await?;\nlet h = channels.iter().find(|c| c.name == \"general\").unwrap().id.to_string();","handlingStrategy":"validation","validationCode":"// Confirm the channel exists in this community before mutating it\nlet channel = client.get_channel(community, channel_uuid).await;\nmatch channel {\n    Ok(ch) => ch,\n    Err(_) => anyhow::bail!(\"channel {channel_uuid} not found — refresh channel list\"),\n}","typeGuard":null,"tryCatchPattern":"match validate_admin_event(&tenant, kind, &event, &state).await {\n    Err(e) if e.to_string().contains(\"channel not found\") => {\n        // Do not retry with the same UUID; refresh the channel list and re-target.\n        // Also check DB health — real DB errors surface through this same message.\n        refresh_channels_and_abort(&event)\n    }\n    other => other,\n}","preventionTips":["Cache channel UUIDs with a TTL and re-resolve when an admin event fails validation","Never hand-type UUIDs into events; copy from a channels-list API response","Treat a burst of these errors across many channels as a possible DB outage, not bad ids"],"tags":["nostr","nip-29","group-admin","channel","not-found"],"backgroundTag":"channel-not-found","analyzedSha":"f956e6fe06a76e50cbd8fba1a162482e752e7f1a","analyzedAt":"2026-08-16T22:11:40.750Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}