{"record":{"id":"41631d34e8765485","repo":"zeroclaw-labs/zeroclaw","slug":"destination-is-not-in-allowed-list","errorCode":null,"errorMessage":"Destination {} is not in allowed list","messagePattern":"Destination (.+?) is not in allowed list","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/clawdtalk.rs","lineNumber":66,"sourceCode":"\n    /// Check if a destination is allowed\n    fn is_destination_allowed(&self, destination: &str) -> bool {\n        if self.allowed_destinations.is_empty() {\n            return true;\n        }\n        self.allowed_destinations.iter().any(|pattern| {\n            pattern == \"*\" || destination.starts_with(pattern) || pattern == destination\n        })\n    }\n\n    /// Initiate an outbound call via Telnyx\n    pub async fn initiate_call(\n        &self,\n        to: &str,\n        _prompt: Option<&str>,\n    ) -> anyhow::Result<CallSession> {\n        if !self.is_destination_allowed(to) {\n            anyhow::bail!(\"Destination {} is not in allowed list\", to);\n        }\n\n        let request = CallRequest {\n            connection_id: self.connection_id.clone(),\n            to: to.to_string(),\n            from: self.from_number.clone(),\n            answering_machine_detection: Some(AnsweringMachineDetection {\n                mode: \"premium\".to_string(),\n            }),\n            webhook_url: None,\n            // AI voice settings via Telnyx Call Control\n            command_id: None,\n        };\n\n        let response = self\n            .client\n            .post(format!(\"{}/calls\", Self::TELNYX_API_URL))\n            .header(\"Authorization\", format!(\"Bearer {}\", self.api_key))","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/clawdtalk.rs#L48-L84","documentation":"ClawdTalkChannel::initiate_call refuses to dial a destination that does not pass the channel's allowed_destinations check before any Telnyx request is made. An empty list means allow-all; otherwise a destination passes only on exact match, prefix match (starts_with, e.g. \"+1555\"), or the wildcard \"*\". This is a deliberate toll-fraud guard, so the error means local policy, not Telnyx, rejected the call. It surfaces through Channel::send because send() passes message.recipient straight into initiate_call.","triggerScenarios":"Calling initiate_call(\"+14449876543\") while allowed_destinations = [\"+1555\"] (prefix mismatch); a SendMessage whose recipient shares no prefix with any allowlist entry; entries or recipients formatted differently (\"+1 555 987 6543\" vs \"+1555\") so starts_with fails; allowlist edited to a new country prefix while old numbers still in use.","commonSituations":"allow_destinations scoped to US +1 numbers while the agent tries an international destination; config migration normalizing one side but not the other; developer assumes substring matching but the code only does prefix/exact/wildcard; post-incident lockdown rejecting numbers the demo used.","solutions":["Add the destination or its E.164 prefix (e.g. \"+1617\") to allowed_destinations under [channels.clawdtalk.<alias>]","Normalize both allowlist entries and message.recipient to canonical E.164 (+countrycode, no spaces/dashes) so starts_with behaves","Use \"*\" or an empty list only in sandbox environments — both mean allow-all","If the number is genuinely unauthorized, treat the bail as correct and audit who requested the call"],"exampleFix":"# before\n[channels.clawdtalk.default]\nallowed_destinations = [\"+1555\"]\n\n# after — permit the number's prefix explicitly\n[channels.clawdtalk.default]\nallowed_destinations = [\"+1555\", \"+1617\"]","handlingStrategy":"validation","validationCode":"// Mirror of the channel's own check (is_destination_allowed is private):\nfn destination_allowed(allowed: &[String], dest: &str) -> bool {\n    allowed.is_empty()\n        || allowed\n            .iter()\n            .any(|p| p == \"*\" || dest.starts_with(p) || p == dest)\n}\n\nif !destination_allowed(&allowed_destinations, &msg.recipient) {\n    // fix config or skip: initiate_call will bail with this exact error\n}","typeGuard":null,"tryCatchPattern":"match channel.send(&msg).await {\n    Err(e) if e.to_string().contains(\"not in allowed list\") => {\n        // policy rejection — never retry the same destination\n    }\n    r => r?,\n}","preventionTips":["Keep allowed_destinations and recipients in the same E.164 format","Treat this error as non-retryable — the same number always fails again","Unit-test the prefix list (exact/prefix/wildcard/empty) whenever it changes"],"tags":["clawdtalk","telnyx","voice","allowlist","config"],"backgroundTag":"allowlist-destination-rejected","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}