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
- Chunk the text to at most max_text_length chars per call and synthesize sequentially (keep chunks at char boundaries).
- Raise the cap in config if the provider tolerates it: [tts] max_text_length = 8192.
- 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
- Chunk long text (paragraph or sentence boundaries beat hard char splits) before synthesis.
- Set [tts] max_text_length explicitly in config so the limit is an intentional number, not the 4096 default.
- Remember the limit counts Unicode chars, not bytes — CJK and emoji text is measured per character.
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
- TTS text must not be empty
- ElevenLabs voice ID contains invalid characters: {voice}
- Edge TTS binary_path must be a bare command name without pat
- Edge TTS binary_path must be one of {:?}, got: {raw_path}
- TTS returned empty audio
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/b08f14102eaa22e3.
Report an issue: GitHub.