{"record":{"id":"39af39db506ca877","repo":"block/buzz","slug":"policy-nobody-this-agent-has-disabled-external-c","errorCode":null,"errorMessage":"policy:nobody — this agent has disabled external channel additions","messagePattern":"policy:nobody — this agent has disabled external channel additions","errorType":"validation","errorClass":"IngestError::Rejected","httpStatus":null,"severity":"error","filePath":"crates/buzz-relay/src/handlers/side_effects.rs","lineNumber":435,"sourceCode":"            // Third-party add: check channel_add_policy on the target.\n            if let Some((policy, owner)) = state\n                .db\n                .get_agent_channel_policy(tenant.community(), &target_pubkey)\n                .await?\n            {\n                match policy.as_str() {\n                    \"owner_only\" => {\n                        let owner_bytes = owner.ok_or_else(|| {\n                            anyhow::anyhow!(\"policy:owner_only — agent has no owner set\")\n                        })?;\n                        if actor_bytes != owner_bytes {\n                            return Err(anyhow::anyhow!(\n                                \"policy:owner_only — only the agent owner can add this agent\"\n                            ));\n                        }\n                    }\n                    \"nobody\" => {\n                        return Err(anyhow::anyhow!(\n                            \"policy:nobody — this agent has disabled external channel additions\"\n                        ));\n                    }\n                    // \"anyone\" or any unknown value → allow.\n                    // NOTE: DB ENUM constraint prevents unknown values from being stored.\n                    // If a new policy value is added to the ENUM, update this match.\n                    _ => {}\n                }\n            }\n\n            Ok(())\n        }\n        9001 => {\n            // REMOVE_USER: self-remove allowed unless actor is the last owner; removing others requires owner/admin\n            let target_pubkey =\n                extract_p_tag(event).ok_or_else(|| anyhow::anyhow!(\"missing p tag\"))?;\n            if target_pubkey == actor_bytes {\n                // Self-removal: must be an active member, and cannot be the last owner.","sourceCodeStart":417,"sourceCodeEnd":453,"githubUrl":"https://github.com/block/buzz/blob/f956e6fe06a76e50cbd8fba1a162482e752e7f1a/crates/buzz-relay/src/handlers/side_effects.rs#L417-L453","documentation":"The target agent's channel_add_policy is 'nobody': the agent has disabled all external channel additions. The relay rejects the 9000 add regardless of who signs it — only the agent's own self-add path (target == actor) goes through, since that returns Ok before the policy match. The DB ENUM constraint means only owner_only/nobody/anyone can be stored, so this is a deliberate opt-out, not an unknown value.","triggerScenarios":"Any third-party 9000 naming an agent whose policy row is 'nobody'; bulk onboarding flows that sweep agent pubkeys into channels; attempting to add a retired or sandboxed agent that was locked down.","commonSituations":"Agents configured for single-channel use whose operators set nobody after setup; post-incident lockdown of an agent that was being spammed into channels; shared directories still listing agents that have since opted out.","solutions":["Have the agent join the channel itself (self-add) if membership is desired","If external adds should be allowed again, the agent/operator changes the policy away from 'nobody'","Remove the agent from bulk-add lists when its policy is nobody, and surface policy state in admin tooling"],"exampleFix":"// before\nput_user(ch, agent_pk).publish_as(anyone_keys)\n// → policy:nobody — this agent has disabled external channel additions\n\n// after: agent self-adds\nput_user(ch, agent_own_pk).publish_as(agent_keys); // target == actor → allowed","handlingStrategy":"validation","validationCode":"// Respect the opt-out before publishing\nlet policy = client.get_agent_channel_policy(community, target_pubkey).await?;\nanyhow::ensure!(\n    policy.as_deref() != Some(\"nobody\"),\n    \"agent has disabled external channel additions — ask it to self-join\"\n);","typeGuard":"const blocksExternalAdds = (policy: string | null): boolean => policy === \"nobody\";","tryCatchPattern":"match validate_admin_event(&tenant, 9000, &event, &state).await {\n    Err(e) if e.to_string().contains(\"policy:nobody\") => {\n        // hard opt-out: no signer can pass; trigger the agent's own join flow instead\n        invite_agent_self_join(target_pubkey, channel_id).await\n    }\n    other => other,\n}","preventionTips":["Treat policy:nobody as a permanent 403 for external adds — do not build retry logic around it","Ask agents to self-join (self-add bypasses policy) via your normal agent-automation channel","Keep agent directories in sync with policy state so retired/locked agents drop out of add lists"],"tags":["nostr","nip-29","agents","policy","opt-out"],"backgroundTag":"agent-add-policy-denied","analyzedSha":"f956e6fe06a76e50cbd8fba1a162482e752e7f1a","analyzedAt":"2026-08-16T22:11:40.750Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}