jamiepine/voicebox · error · RuntimeError

Unable to split unstable TTS output for retry

Error message

Unable to split unstable TTS output for retry

What it means

Raised by generate_one() in backend/utils/chunked_tts.py when the runaway detector flagged a chunk, the retry budget and minimum-size checks passed, but split_text_into_chunks(chunk_text, retry_max_chars) returned only a single chunk. The retry strategy depends on splitting the unstable chunk into strictly smaller pieces; if the splitter cannot subdivide it (e.g. no sentence/whitespace boundaries within the budget), retry is impossible and the function aborts.

Source

Thrown at backend/utils/chunked_tts.py:275

    ) -> tuple[np.ndarray, int]:
        chunk_audio, chunk_sr = await backend.generate(
            chunk_text,
            voice_prompt,
            language,
            chunk_seed,
            instruct,
        )

        if runaway_detector is not None and runaway_detector(chunk_audio, chunk_sr):
            if retry_depth >= MAX_RUNAWAY_RETRIES or len(chunk_text) <= MIN_RUNAWAY_RETRY_CHARS:
                raise RuntimeError(
                    "TTS output remained unstable after retrying smaller text chunks"
                )

            retry_max_chars = max(MIN_RUNAWAY_RETRY_CHARS, len(chunk_text) // 2)
            retry_chunks = split_text_into_chunks(chunk_text, retry_max_chars)
            if len(retry_chunks) <= 1:
                raise RuntimeError("Unable to split unstable TTS output for retry")

            logger.warning(
                "Detected unstable TTS output for %d chars; retrying as %d smaller chunks",
                len(chunk_text),
                len(retry_chunks),
            )
            retry_audio: list[np.ndarray] = []
            for i, retry_text in enumerate(retry_chunks):
                retry_seed = (
                    chunk_seed + ((retry_depth + 1) * 1000) + i
                    if chunk_seed is not None
                    else None
                )
                audio, sample_rate = await generate_one(
                    retry_text,
                    retry_seed,
                    retry_depth + 1,
                )

View on GitHub (pinned to 51f49dea19)

Solutions

  1. Pre-split the input on whitespace or character count before passing to chunked TTS so no single chunk is unsplittable.
  2. Strip or break up long unbroken tokens (URLs, base64) in the source text.
  3. Provide a custom trim_fn or preprocessor that normalizes problematic text.
  4. For CJK/no-space languages, configure the chunker to split on characters, not sentence/word boundaries.

Example fix

// before: a chunk that is one long token run cannot be split
audio, sr = await generate_chunked_tts(backend, very_long_token_run, voice_prompt, ...)
// after: break long token runs before synthesis
text = re.sub(r'(\S{200})', r'\1 ', text)  // insert breakable spaces
audio, sr = await generate_chunked_tts(backend, text, voice_prompt, ...)
Defensive patterns

Strategy: validation

Validate before calling

import re

def is_splittable(text: str, max_chars: int) -> bool:
    # ensure there is at least one breakable boundary within max_chars
    broken = re.findall(r'.{1,' + str(max_chars) + r'}', text)
    return len(broken) > 1

Try / catch

try:
    audio, sr = await generate_chunked_tts(backend, text, voice_prompt, ...)
except RuntimeError as e:
    if 'Unable to split unstable TTS output' in str(e):
        # insert breakable spaces into long token runs and retry
        text = re.sub(r'(\S{120})', r'\1 ', text)
        audio, sr = await generate_chunked_tts(backend, text, voice_prompt, ...)
    else:
        raise

Prevention

When it happens

Trigger: A chunk whose text has no splittable boundary under retry_max_chars — a single very long word/URL/token run, or text without sentence punctuation/whitespace where the splitter cannot break it; chunk length just over MIN_RUNAWAY_RETRY_CHARS so halving still yields one piece per the splitter's rules.

Common situations: Inputs containing very long unbroken token strings (URLs, base64, CJK without spaces, code); text with no terminal punctuation that the sentence-aware splitter keys on; languages without whitespace word boundaries.

Related errors


AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12). Data as JSON: /api/errors/275769ae2b0bce32. Report an issue: GitHub.