{"record":{"id":"0fa14c85dba3c608","repo":"zeroclaw-labs/zeroclaw","slug":"agent-has-no-tts-provider-configured-set-agent","errorCode":null,"errorMessage":"Agent has no tts_provider configured. Set `agent.<alias>.tts_provider = \"<type>.<alias>\"` referencing a [providers.tts.<type>.<alias>] entry.","messagePattern":"Agent has no tts_provider configured\\. Set `agent\\.<alias>\\.tts_provider = \"<type>\\.<alias>\"` referencing a \\[providers\\.tts\\.<type>\\.<alias>\\] entry\\.","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/tts.rs","lineNumber":1089,"sourceCode":"\n    pub async fn synthesize_opus(&self, text: &str) -> Result<Vec<u8>> {\n        let audio = self.synthesize(text).await?;\n        let provider_alias = self.agent_tts_provider.as_str();\n        let format = self\n            .tts_providers\n            .get(provider_alias)\n            .map(|p| p.output_format())\n            .unwrap_or(\"unknown\");\n        if format == \"opus\" {\n            return Ok(audio);\n        }\n        transcode_to_opus(audio).await\n    }\n\n    pub async fn synthesize(&self, text: &str) -> Result<Vec<u8>> {\n        let provider_alias = self.agent_tts_provider.as_str();\n        if provider_alias.is_empty() {\n            bail!(\n                \"Agent has no tts_provider configured. Set \\\n                 `agent.<alias>.tts_provider = \\\"<type>.<alias>\\\"` referencing a \\\n                 [providers.tts.<type>.<alias>] entry.\"\n            );\n        }\n        let voice = self\n            .voice_by_alias\n            .get(provider_alias)\n            .map_or(self.default_voice.as_str(), String::as_str);\n        self.synthesize_with_provider(text, provider_alias, voice)\n            .await\n    }\n\n    /// Synthesize text using the runtime-active agent's resolved\n    /// `tts_provider` reference and an explicit voice.\n    pub async fn synthesize_with_voice(&self, text: &str, voice: &str) -> Result<Vec<u8>> {\n        let provider_alias = self.agent_tts_provider.as_str();\n        if provider_alias.is_empty() {","sourceCodeStart":1071,"sourceCodeEnd":1107,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/tts.rs#L1071-L1107","documentation":"TtsManager resolves the active agent's tts_provider (channel-owning agent, else the runtime-active agent) at construction; an empty resolved value means no agent binding or an explicit opt-out. synthesize fails loud with this message instead of silently guessing a provider, and the message spells out the exact config join: agent.<alias>.tts_provider = \"<type>.<alias>\" referencing a [providers.tts.<type>.<alias>] entry.","triggerScenarios":"The agent block exists but tts_provider is unset/empty; the channel is built with an agent alias that does not exist in [agents.*] so the lookup falls through to empty; TtsManager::from_config was used with no runtime-active agent configured.","commonSituations":"A fresh install defines [providers.tts.*] but forgets the per-agent join, assuming the provider list alone enables TTS. Renaming an agent without updating the channel's owning-agent alias also yields an empty resolution. Note the dotted value must be family.alias (e.g. \"edge.main\"), not just the family.","solutions":["Set the join on the agent: agent.<alias>.tts_provider = \"edge.main\" (family.alias), matching an existing [providers.tts.edge.main] entry.","Verify the agent alias the channel binds to actually exists under [agents.*] — a typo resolves to empty and lands here.","Restart the runtime after the config change; TtsManager resolves the value once at construction.","Check the runtime logs for \"Skipping TTS provider\" warnings — a provider that failed validation (e.g. missing api_key) is not registered, so the dotted alias would also miss."],"exampleFix":"# before — provider defined, agent join missing\n[providers.tts.edge.main]\nvoice = \"en-US-AriaNeural\"\n\n[agents.butler]\nmodel_provider = \"openai.main\"\n\n# after\n[providers.tts.edge.main]\nvoice = \"en-US-AriaNeural\"\n\n[agents.butler]\nmodel_provider = \"openai.main\"\ntts_provider = \"edge.main\"","handlingStrategy":"validation","validationCode":"// Fail fast at startup instead of at first synthesis.\nfn assert_agent_tts_join(config: &Config, agent_alias: &str) -> anyhow::Result<()> {\n    let agent = config\n        .agents\n        .get(agent_alias)\n        .ok_or_else(|| anyhow::anyhow!(\"agent [{agent_alias}] not defined\"))?;\n    anyhow::ensure!(\n        !agent.tts_provider.trim().is_empty(),\n        \"set [{agent_alias}] tts_provider = \\\"<family>.<alias>\\\"\"\n    );\n    Ok(())\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always pair a [providers.tts.<family>.<alias>] entry with agent.<alias>.tts_provider = \"<family>.<alias>\"; the provider list alone enables nothing.","Watch startup logs for \"Skipping TTS provider\" — a provider that failed validation never registers, so the dotted join would miss even when written.","Include the tts_provider join in config template/lint checks so new agents cannot ship without it."],"tags":["tts","config","agent","setup"],"backgroundTag":"missing-config-value","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}