zeroclaw-labs/zeroclaw · error

Edge TTS binary_path must be one of {:?}, got: {raw_path}

Error message

Edge TTS binary_path must be one of {:?}, got: {raw_path}

What it means

After the path-separator check, EdgeTtsProvider::new requires binary_path to be exactly one of ALLOWED_BINARIES = ["edge-tts", "edge-playback"]. Together with the separator rule this closes the allowlist: config cannot make the runtime execute any other binary name, so a hostile config edit cannot swap in an arbitrary command.

Source

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

}

impl EdgeTtsProvider {
    /// Allowed basenames for the Edge TTS binary.
    const ALLOWED_BINARIES: &[&str] = &["edge-tts", "edge-playback"];

    pub fn new(alias: &str, config: &TtsProviderConfig) -> Result<Self> {
        let raw_path = config
            .binary_path
            .clone()
            .filter(|p| !p.trim().is_empty())
            .unwrap_or_else(|| "edge-tts".to_string());
        if raw_path.contains('/') || raw_path.contains('\\') {
            bail!(
                "Edge TTS binary_path must be a bare command name without path separators, got: {raw_path}"
            );
        }
        if !Self::ALLOWED_BINARIES.contains(&raw_path.as_str()) {
            bail!(
                "Edge TTS binary_path must be one of {:?}, got: {raw_path}",
                Self::ALLOWED_BINARIES,
            );
        }
        Ok(Self {
            alias: alias.to_string(),
            binary_path: raw_path,
            #[cfg(test)]
            binary_args: Vec::new(),
            timeout: TTS_HTTP_TIMEOUT,
        })
    }

    /// Test-only constructor that accepts a script path and timeout so tests
    /// can drive the `edge-tts` subprocess. The production [`new`](Self::new)
    /// allowlist stays a security boundary; this exists only in Unix test builds.
    #[cfg(all(test, unix))]
    fn new_with_binary(alias: &str, binary_path: &str, timeout: std::time::Duration) -> Self {

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Use exactly "edge-tts" (default) or "edge-playback".
  2. On Windows, install the pip package (`pip install edge-tts`); the .exe is then found by its bare name through PATH.
  3. If you need a pinned/wrapped binary, expose it under the exact name "edge-tts" earlier in PATH instead of renaming it in config.

Example fix

# before
[providers.tts.edge.main]
binary_path = "edge-tts.exe"

# after
[providers.tts.edge.main]
binary_path = "edge-tts"
Defensive patterns

Strategy: validation

Validate before calling

const ALLOWED: &[&str] = &["edge-tts", "edge-playback"];
if let Some(bin) = &edge_cfg.binary_path {
    assert!(ALLOWED.contains(&bin.as_str()), "binary_path must be one of {ALLOWED:?}");
}

Type guard

fn is_allowed_edge_binary(p: &str) -> bool {
    matches!(p, "edge-tts" | "edge-playback")
}

Prevention

When it happens

Trigger: binary_path = "edge-tts.exe" (Windows suffix), "edge_tts" (underscore), a renamed wrapper script like "edge-tts-custom", or a package-manager alias. Everything except the two exact strings is rejected at construction time.

Common situations: Windows users write the .exe suffix out of habit. Teams rename a pinned wrapper for version control and forget the allowlist. The error names the allowed values verbatim, so the fix is mechanical.

Understand the failure class

Background: Config validation failed: what "invalid value for {key}" and settings-rejection errors mean across 19 open-source libraries — this error's family across 19 libraries.

Related errors


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