zeroclaw-labs/zeroclaw · error · anyhow::Error

edge-tts failed (exit {}): {}

Error message

edge-tts failed (exit {}): {}

What it means

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.

Source

Thrown at crates/zeroclaw-channels/src/tts.rs:797

                    // deadline used above, so a child that ignores the kill
                    // cannot hold `synthesize` past the provider timeout; the
                    // artifact guard's `Drop` still owns the final reap.
                    let _ = tokio::time::timeout_at(deadline, child.kill()).await;
                    let _ = tokio::time::timeout_at(deadline, child.wait()).await;
                    return Err(err).context("Failed to wait for edge-tts subprocess");
                }
                Err(_elapsed) => {
                    reader.abort();
                    let _ = reader.await;
                    let _ = tokio::time::timeout_at(deadline, child.kill()).await;
                    let _ = tokio::time::timeout_at(deadline, child.wait()).await;
                    bail!("Edge TTS subprocess timed out");
                }
            }
        };

        if !status.success() {
            bail!("edge-tts failed (exit {}): {}", status, stderr);
        }

        let bytes = tokio::fs::read(&output_file)
            .await
            .context("Failed to read edge-tts output file")?;

        Ok(bytes)
    }

    fn supported_voices(&self) -> Vec<String> {
        // Edge TTS has many voices; return common defaults.
        [
            "en-US-AriaNeural",
            "en-US-GuyNeural",
            "en-US-JennyNeural",
            "en-GB-SoniaNeural",
        ]
        .iter()

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Read the stderr portion of the message; it is the CLI's own error text and pinpoints the cause.
  2. For voice errors, use a full neural voice name such as en-US-AriaNeural or en-GB-SoniaNeural.
  3. For HTTP/SSL/403 errors, upgrade: `pip install -U edge-tts`.
  4. Re-run the failing command manually with the same --text/--voice/--write-media arguments to confirm the fix.

Example fix

# before — short voice name, CLI exits non-zero
[providers.tts.edge.main]
voice = "Aria"

# after — full Edge voice identifier
[providers.tts.edge.main]
voice = "en-US-AriaNeural"
Defensive patterns

Strategy: try-catch

Validate before calling

const KNOWN_EDGE_VOICES: &[&str] = &[
    "en-US-AriaNeural", "en-US-GuyNeural", "en-US-JennyNeural", "en-GB-SoniaNeural",
];
// Fall back to a known-good full voice name before spawning the subprocess.
let voice = if KNOWN_EDGE_VOICES.contains(&voice) { voice } else { "en-US-AriaNeural" };

Type guard

fn is_full_edge_voice(v: &str) -> bool {
    // full neural names end in "Neural" and carry a language prefix like en-US-
    v.ends_with("Neural") && v.contains('-')
}

Try / catch

match mgr.synthesize(text).await {
    Ok(audio) => Ok(audio),
    Err(err) if err.to_string().contains("edge-tts failed") => {
        // stderr is embedded in the message; map common causes
        let msg = err.to_string();
        if msg.contains("Invalid voice") || msg.contains("voice") {
            Err(err.context("use a full voice id like en-US-AriaNeural"))
        } else if msg.contains("403") || msg.contains("SSL") || msg.contains("Errno") {
            Err(err.context("edge-tts too old or offline: pip install -U edge-tts"))
        } else {
            Err(err)
        }
    }
    Err(err) => Err(err),
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/bf98c47f8279a9ad. Report an issue: GitHub.