{"record":{"id":"bf98c47f8279a9ad","repo":"zeroclaw-labs/zeroclaw","slug":"edge-tts-failed-exit","errorCode":null,"errorMessage":"edge-tts failed (exit {}): {}","messagePattern":"edge-tts failed \\(exit (.+?)\\): (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/tts.rs","lineNumber":797,"sourceCode":"                    // deadline used above, so a child that ignores the kill\n                    // cannot hold `synthesize` past the provider timeout; the\n                    // artifact guard's `Drop` still owns the final reap.\n                    let _ = tokio::time::timeout_at(deadline, child.kill()).await;\n                    let _ = tokio::time::timeout_at(deadline, child.wait()).await;\n                    return Err(err).context(\"Failed to wait for edge-tts subprocess\");\n                }\n                Err(_elapsed) => {\n                    reader.abort();\n                    let _ = reader.await;\n                    let _ = tokio::time::timeout_at(deadline, child.kill()).await;\n                    let _ = tokio::time::timeout_at(deadline, child.wait()).await;\n                    bail!(\"Edge TTS subprocess timed out\");\n                }\n            }\n        };\n\n        if !status.success() {\n            bail!(\"edge-tts failed (exit {}): {}\", status, stderr);\n        }\n\n        let bytes = tokio::fs::read(&output_file)\n            .await\n            .context(\"Failed to read edge-tts output file\")?;\n\n        Ok(bytes)\n    }\n\n    fn supported_voices(&self) -> Vec<String> {\n        // Edge TTS has many voices; return common defaults.\n        [\n            \"en-US-AriaNeural\",\n            \"en-US-GuyNeural\",\n            \"en-US-JennyNeural\",\n            \"en-GB-SoniaNeural\",\n        ]\n        .iter()","sourceCodeStart":779,"sourceCodeEnd":815,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/tts.rs#L779-L815","documentation":"The edge-tts subprocess exited within the timeout but with a non-zero status. synthesize drains the child's stderr concurrently (so the pipe cannot deadlock) and includes the exit code and stderr text in the error, so the message itself carries the CLI's own diagnosis.","triggerScenarios":"Unknown or retired voice name passed via --voice; edge-tts version too old for the current Microsoft endpoint (exits with an HTTP/SSL error on stderr); no internet (urllib/requests error on stderr); broken install (missing Python deps) producing an ImportError trace on stderr.","commonSituations":"Voice strings like \"Aria\" or \"en-US-Aria\" instead of the full \"en-US-AriaNeural\". Microsoft changes the endpoint auth roughly yearly and old pip releases die with a 403 on stderr; upgrading fixes it. Read the stderr tail first — it names the exact CLI failure.","solutions":["Read the stderr portion of the message; it is the CLI's own error text and pinpoints the cause.","For voice errors, use a full neural voice name such as en-US-AriaNeural or en-GB-SoniaNeural.","For HTTP/SSL/403 errors, upgrade: `pip install -U edge-tts`.","Re-run the failing command manually with the same --text/--voice/--write-media arguments to confirm the fix."],"exampleFix":"# before — short voice name, CLI exits non-zero\n[providers.tts.edge.main]\nvoice = \"Aria\"\n\n# after — full Edge voice identifier\n[providers.tts.edge.main]\nvoice = \"en-US-AriaNeural\"","handlingStrategy":"try-catch","validationCode":"const KNOWN_EDGE_VOICES: &[&str] = &[\n    \"en-US-AriaNeural\", \"en-US-GuyNeural\", \"en-US-JennyNeural\", \"en-GB-SoniaNeural\",\n];\n// Fall back to a known-good full voice name before spawning the subprocess.\nlet voice = if KNOWN_EDGE_VOICES.contains(&voice) { voice } else { \"en-US-AriaNeural\" };","typeGuard":"fn is_full_edge_voice(v: &str) -> bool {\n    // full neural names end in \"Neural\" and carry a language prefix like en-US-\n    v.ends_with(\"Neural\") && v.contains('-')\n}","tryCatchPattern":"match mgr.synthesize(text).await {\n    Ok(audio) => Ok(audio),\n    Err(err) if err.to_string().contains(\"edge-tts failed\") => {\n        // stderr is embedded in the message; map common causes\n        let msg = err.to_string();\n        if msg.contains(\"Invalid voice\") || msg.contains(\"voice\") {\n            Err(err.context(\"use a full voice id like en-US-AriaNeural\"))\n        } else if msg.contains(\"403\") || msg.contains(\"SSL\") || msg.contains(\"Errno\") {\n            Err(err.context(\"edge-tts too old or offline: pip install -U edge-tts\"))\n        } else {\n            Err(err)\n        }\n    }\n    Err(err) => Err(err),\n}","preventionTips":["Always log the stderr tail embedded in the error — it is the CLI's own diagnosis and points at the fix.","Use full neural voice identifiers (en-US-AriaNeural), not short forms.","Schedule `pip install -U edge-tts` on your hosts; Microsoft endpoint changes break old releases with non-zero exits."],"tags":["edge-tts","tts","subprocess","stderr"],"backgroundTag":"subprocess-exit-error","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}