{"record":{"id":"485987abc4317860","repo":"block/buzz","slug":"only-owner-can-delete-group","errorCode":null,"errorMessage":"only owner can delete group","messagePattern":"only owner can delete group","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/buzz-relay/src/handlers/side_effects.rs","lineNumber":671,"sourceCode":"                }\n            }\n        }\n        9008 => {\n            // DELETE_GROUP: owner only, or the owning human of the channel's agent-owner.\n            let members = state.db.get_members(tenant.community(), channel_id).await?;\n            let actor_member = members.iter().find(|m| m.pubkey == actor_bytes);\n            match actor_member {\n                Some(m) if m.role == \"owner\" => Ok(()),\n                _ => {\n                    // Allow the owning human of any active owner-role agent in the\n                    // channel, even when the human is not a channel member —\n                    // diverges from kind:9001 intentionally.\n                    if actor_owns_any_owner_agent(state, tenant.community(), &members, &actor_bytes)\n                        .await?\n                    {\n                        return Ok(());\n                    }\n                    Err(anyhow::anyhow!(\"only owner can delete group\"))\n                }\n            }\n        }\n        9022 => {\n            // LEAVE_REQUEST: must be an active member, and cannot be the last owner.\n            // Identical rule to kind:9001 self-removal, including its wording.\n            let members = state.db.get_members(tenant.community(), channel_id).await?;\n            channel_authz::decide_self_departure(&members, &actor_bytes)?;\n            Ok(())\n        }\n        _ => Ok(()),\n    }\n}\n\n/// Emit a system message (kind 40099) signed by the relay keypair.\n///\n/// `idempotency_ts` is used as the event's `created_at`. Passing a stable\n/// timestamp (e.g. from the outbox row's `created_at`) makes re-tries produce","sourceCodeStart":653,"sourceCodeEnd":689,"githubUrl":"https://github.com/block/buzz/blob/dad5a33865fc81a2e55b3b60746632f615ec1e3a/crates/buzz-relay/src/handlers/side_effects.rs#L653-L689","documentation":"kind:9008 (DELETE_GROUP) is intentionally stricter than other channel ops: the actor must be a member with role 'owner', or the owning human of an active owner-role agent in the channel (a deliberate divergence from kind:9001, so a human can tear down a group their agent owns). Admins cannot delete groups. Any other actor — admin, plain member, non-member — gets this rejection after the member list is fetched.","triggerScenarios":"An admin-role member publishes 9008; an owner's key is no longer in the members list (role changed, membership removed); the acting human never registered ownership of the owner-role agent; a moderator bot with admin role attempts group deletion.","commonSituations":"Assuming admin inherits all owner powers; owner key rotated without updating membership; the owning human relation missing because the agent was provisioned manually.","solutions":["Have a member with role 'owner' publish the 9008 event (verify with get_members / relay_members).","If the owner should be the human behind an agent, ensure that agent has owner role and the human's key is registered as its owner (is_agent_owner path).","Promote the intended actor to owner first if group deletion is the goal — admin is insufficient by design.","Check you are acting in the right channel: the members list consulted is the one for the h-tag channel."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// gate 9008 sends on verified owner standing before publishing\nconst members = await relay.channelMembers(channelId);\nconst me = members.find((m) => m.pubkey === myPubkey);\nconst iOwnOwnerAgent = await ownsAnyOwnerAgent(relay, channelId, myPubkey);\nif (!(me?.role === 'owner' || iOwnOwnerAgent)) {\n  throw new Error('only the channel owner (or an owner-agent\\'s human) may delete the group');\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Reserve 9008 in tooling for owner-role keys; surface a clear 'ask the owner' message for admins.","After role changes or owner key rotation, verify get_members still lists the intended owner before destructive ops.","When agents own channels, record the owning human at creation so the is_agent_owner path works."],"tags":["buzz-relay","nostr","delete-group","kind-9008","authorization"],"backgroundTag":"permission-denied","analyzedSha":"dad5a33865fc81a2e55b3b60746632f615ec1e3a","analyzedAt":"2026-08-20T04:38:24.874Z","contentChangedAt":"2026-08-20T04:38:24.874Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}