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

TTS text too long ({} chars, max {})

Error message

TTS text too long ({} chars, max {})

What it means

synthesize_with_provider counts Unicode chars (chars().count(), not bytes) and rejects text longer than max_text_length. The limit comes from config.tts.max_text_length and defaults to 4096 when unset or zero. The check runs before any provider call, so oversized input never reaches the upstream API.

Source

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

            );
        }
        self.synthesize_with_provider(text, provider_alias, voice)
            .await
    }

    /// Synthesize text using a specific dotted-alias model_provider and voice.
    pub async fn synthesize_with_provider(
        &self,
        text: &str,
        provider_alias: &str,
        voice: &str,
    ) -> Result<Vec<u8>> {
        if text.is_empty() {
            bail!("TTS text must not be empty");
        }
        let char_count = text.chars().count();
        if char_count > self.max_text_length {
            bail!(
                "TTS text too long ({} chars, max {})",
                char_count,
                self.max_text_length
            );
        }

        let tts = self.tts_providers.get(provider_alias).ok_or_else(|| {
            let available = self.available_providers().join(", ");
            ::zeroclaw_log::record!(
                ERROR,
                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)
                    .with_outcome(::zeroclaw_log::EventOutcome::Failure)
                    .with_attrs(::serde_json::json!({
                        "tts_provider": provider_alias,
                        "available": available,
                    })),
                "tts: provider not configured"
            );

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Chunk the text to at most max_text_length chars per call and synthesize sequentially (keep chunks at char boundaries).
  2. Raise the cap in config if the provider tolerates it: [tts] max_text_length = 8192.
  3. Trim boilerplate/markers from the text before measuring.

Example fix

// before — one call, bails over the 4096-char default cap
let audio = mgr.synthesize(&long_post).await?;

// after — split by Unicode chars, synthesize per chunk
fn split_unicode_chars(text: &str, max: usize) -> Vec<String> {
    let mut out = Vec::new();
    let mut cur = String::new();
    for ch in text.chars() {
        cur.push(ch);
        if cur.chars().count() == max {
            out.push(std::mem::take(&mut cur));
        }
    }
    if !cur.is_empty() { out.push(cur); }
    out
}
for chunk in split_unicode_chars(&long_post, 4000) {
    let part = mgr.synthesize(&chunk).await?;
    send_voice(&part).await?;
}
Defensive patterns

Strategy: validation

Validate before calling

// Split on Unicode char boundaries so every chunk satisfies the
// chars()-based limit; keep chunks under the configured cap.
fn split_for_tts(text: &str, max_chars: usize) -> Vec<String> {
    let mut out: Vec<String> = Vec::new();
    let mut cur = String::new();
    let mut n = 0usize;
    for ch in text.chars() {
        cur.push(ch);
        n += 1;
        if n == max_chars {
            out.push(std::mem::take(&mut cur));
            n = 0;
        }
    }
    if n > 0 { out.push(cur); }
    out
}

for chunk in split_for_tts(&text, 4000) {
    let audio = mgr.synthesize(&chunk).await?;
    send_voice(&audio).await?;
}

Type guard

fn fits_tts_limit(text: &str, max_chars: usize) -> bool {
    text.chars().count() <= max_chars
}

Prevention

When it happens

Trigger: Synthesizing a long channel message, article, or transcript in a single call: any text over 4096 chars (or over a lowered tts.max_text_length) bails. Counting is by chars, so CJK/emoji text is measured per character, not per UTF-8 byte.

Common situations: Bridging a chat channel to voice notes without chunking: one long paste exceeds the cap. Operators who lower tts.max_text_length to protect API quotas then see previously-fine messages rejected.

Related errors


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