{"record":{"id":"5898dc6406c20fb8","repo":"block/buzz","slug":"not-a-member","errorCode":null,"errorMessage":"not a member","messagePattern":"not a member","errorType":"validation","errorClass":"IngestError::Rejected","httpStatus":400,"severity":"error","filePath":"crates/buzz-relay/src/handlers/side_effects.rs","lineNumber":626,"sourceCode":"                        )\n                        .await?\n                        {\n                            return Ok(());\n                        }\n                        Err(anyhow::anyhow!(\n                            \"actor not authorized for name/about/archived/visibility/ttl changes\"\n                        ))\n                    }\n                }\n            } else {\n                // topic/purpose: any member\n                let is_member = state\n                    .is_member_cached(tenant.community(), channel_id, &actor_bytes)\n                    .await?;\n                if is_member {\n                    Ok(())\n                } else {\n                    Err(anyhow::anyhow!(\"not a member\"))\n                }\n            }\n        }\n        9005 => {\n            // DELETE_EVENT: event author OR channel owner/admin.\n            if let Some(action_id) = extract_tag_value(event, \"action_id\") {\n                Uuid::parse_str(&action_id)\n                    .map_err(|_| anyhow::anyhow!(\"invalid action_id tag\"))?;\n            }\n\n            // Extract target event from e tag to check authorship.\n            let target_id = event\n                .tags\n                .iter()\n                .find_map(|tag| {\n                    if tag.kind().to_string() == \"e\" {\n                        tag.content().and_then(|v| hex::decode(v).ok())\n                    } else {","sourceCodeStart":608,"sourceCodeEnd":644,"githubUrl":"https://github.com/block/buzz/blob/f956e6fe06a76e50cbd8fba1a162482e752e7f1a/crates/buzz-relay/src/handlers/side_effects.rs#L608-L644","documentation":"Thrown by kind:9002 when the event only carries topic/purpose tags (no privileged tags) and the actor is not a channel member. Topic and purpose edits are the most permissive metadata change — any active member may do them — but non-members may not, and unlike the privileged-tag branch there is no agent-owner fallback.","triggerScenarios":"Publishing kind:9002 with tags like [[\"h\", ch], [\"topic\", \"...\"]] using a key that has no membership row in the channel — someone who never joined, already left, or was removed.","commonSituations":"Bots attempting to set topics in channels they were never invited to; humans editing a topic after an admin removed them; stale channel ids after channel recreation so the membership lookup misses.","solutions":["Join the channel (or get re-added) before editing topic/purpose.","Verify the h tag channel id matches a channel you are currently in (buzz channels list).","If you were removed intentionally, ask a member or admin to make the edit.","Pre-check membership via the members query before publishing in automation."],"exampleFix":"// before — non-member sets the topic\nawait sdk.editChannelMetadata(channelId, { topic: \"Sprint 42\" }); // → \"not a member\"\n\n// after — join first, then edit\nawait sdk.joinChannel(channelId);\nawait sdk.editChannelMetadata(channelId, { topic: \"Sprint 42\" });","handlingStrategy":"validation","validationCode":"// topic/purpose edits only need membership — check it first\nconst members = await buzz.channelsMembersList(channelId);\nif (!members.some((m) => m.pubkey === myPubkey)) {\n  throw new Error(\"Join the channel before editing its topic/purpose\");\n}\nawait sdk.editChannelMetadata(channelId, { topic, purpose });","typeGuard":"function canEditTopicPurpose(members: { pubkey: string }[], pubkey: string): boolean {\n  return members.some((m) => m.pubkey === pubkey);\n}","tryCatchPattern":"try {\n  await sdk.editChannelMetadata(channelId, { topic });\n} catch (e) {\n  if (String(e).includes(\"not a member\")) {\n    ui.toast(\"You must be a member of this channel to edit its topic\");\n  } else throw e;\n}","preventionTips":["Hide topic/purpose edit affordances for channels the user is not a member of.","Re-check membership when opening old channel views — removal revokes edit rights immediately.","Bots should confirm join success before attempting metadata writes."],"tags":["nostr","authorization","channel-membership","kind-9002","relay"],"backgroundTag":"group-membership-required","analyzedSha":"f956e6fe06a76e50cbd8fba1a162482e752e7f1a","analyzedAt":"2026-08-16T22:11:40.750Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}