{"record":{"id":"81d82189b3a926dc","repo":"zeroclaw-labs/zeroclaw","slug":"failed-to-speak","errorCode":null,"errorMessage":"Failed to speak: {}","messagePattern":"Failed to speak: (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/clawdtalk.rs","lineNumber":129,"sourceCode":"            language: \"en-US\".to_string(),\n        };\n\n        let response = self\n            .client\n            .post(format!(\n                \"{}/calls/{}/actions/speak\",\n                Self::TELNYX_API_URL,\n                call_control_id\n            ))\n            .header(\"Authorization\", format!(\"Bearer {}\", self.api_key))\n            .header(\"Content-Type\", \"application/json\")\n            .json(&request)\n            .send()\n            .await?;\n\n        if !response.status().is_success() {\n            let error = response.text().await?;\n            anyhow::bail!(\"Failed to speak: {}\", error);\n        }\n\n        Ok(())\n    }\n\n    /// Hang up an active call\n    pub async fn hangup(&self, call_control_id: &str) -> anyhow::Result<()> {\n        let response = self\n            .client\n            .post(format!(\n                \"{}/calls/{}/actions/hangup\",\n                Self::TELNYX_API_URL,\n                call_control_id\n            ))\n            .header(\"Authorization\", format!(\"Bearer {}\", self.api_key))\n            .send()\n            .await?;\n","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/clawdtalk.rs#L111-L147","documentation":"speak() POSTs the text to Telnyx Call Control /v2/calls/{call_control_id}/actions/speak (payload_type text, service_level premium, voice female, language en-US). Non-2xx embeds Telnyx's error body in this bail. The dominant cause in this channel is timing: Channel::send sleeps a fixed 2 seconds after initiate_call and then speaks, so an unanswered, declined, or already-ended leg makes the call_control_id stale.","triggerScenarios":"Call not answered (or premium answering-machine detection ended the leg) before the fixed 2s sleep in send() elapses; speaking to a call_control_id after hangup; call_control_id from a different Telnyx project than the api_key; account/region rejecting the premium TTS service level or voice/language pair.","commonSituations":"Calls that roll to voicemail; recipients declining quickly; reusing a CallSession captured in a previous run after a restart; accounts without premium TTS provisioned.","solutions":["Only speak on a confirmed-answered leg — drive speak from the call.answered webhook/state instead of the fixed 2-second sleep","Confirm the call_control_id comes from the initiate_call response of the same account","Inspect the embedded Telnyx body for which parameter was rejected (service_level, voice, language)","If premium TTS is unavailable, provision it or lower service_level in SpeakRequest"],"exampleFix":"// before: fixed sleep, speak may hit an unanswered/stale leg\ntokio::time::sleep(Duration::from_secs(2)).await;\nself.speak(&session.call_control_id, &message.content).await?;\n\n// after: wait for the answered state before speaking\nwait_for_call_answered(&session.call_control_id).await?;\nself.speak(&session.call_control_id, &message.content).await?;","handlingStrategy":"try-catch","validationCode":"// Only speak to legs confirmed answered:\n// GET https://api.telnyx.com/v2/calls/{call_control_id} and check state == \"answered\"\nlet state = telnyx_call_state(&session.call_control_id).await?;\nif state != \"answered\" {\n    // skip speak: the leg cannot receive TTS\n}","typeGuard":null,"tryCatchPattern":"if let Err(e) = channel.speak(&session.call_control_id, text).await {\n    // stale or non-answered leg — teardown, don't retry\n    let _ = channel.hangup(&session.call_control_id).await;\n}","preventionTips":["Drive speak from call.answered webhooks, not a fixed sleep","Treat speak failure as terminal for that call leg","Match TelnyxWebhookEvent lifecycle events with the session ids you speak to"],"tags":["clawdtalk","telnyx","tts","voice","call-control"],"backgroundTag":"telnyx-api-error","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}