{"record":{"id":"0a871b4d14f08b94","repo":"block/buzz","slug":"channel-not-found","errorCode":null,"errorMessage":"channel not found","messagePattern":"channel not found","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/buzz-relay/src/handlers/side_effects.rs","lineNumber":336,"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_for_event_write(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":318,"sourceCodeEnd":354,"githubUrl":"https://github.com/block/buzz/blob/dad5a33865fc81a2e55b3b60746632f615ec1e3a/crates/buzz-relay/src/handlers/side_effects.rs#L318-L354","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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"],"exampleFix":"// before\n.map_err(|_| anyhow::anyhow!(\"channel not found\"))?;\n// after\nstate.db.get_channel_for_event_write(tenant.community(), channel_id).await\n    .map_err(|e| match e {\n        buzz_db::Error::NotFound => anyhow::anyhow!(\"channel not found\"),\n        other => anyhow::anyhow!(\"channel lookup failed: {other}\"),\n    })?;","handlingStrategy":"validation","validationCode":"// caller-side precheck via relay query before publishing an admin event\nlet exists = state.db.get_channel_for_event_write(tenant.community(), channel_id).await.is_ok();\nif !exists { return Err(\"target channel does not exist in this community\"); }","typeGuard":"null","tryCatchPattern":"match validate_admin_event(&state, &tenant, &event).await {\n    Err(e) if e.to_string().contains(\"channel not found\") => {\n        // reject event with NACK; check channel id / tenant scoping\n    }\n    other => other?,\n}","preventionTips":["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"],"tags":["rust","nostr","channel","database","validation"],"backgroundTag":"channel-not-found","analyzedSha":"dad5a33865fc81a2e55b3b60746632f615ec1e3a","analyzedAt":"2026-09-05T18:13:50.666Z","contentChangedAt":"2026-09-05T18:13:50.666Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}