babysor/MockingBird · error · RuntimeError

/text-to-speech cue {cue.index}: status={resp.status_code},

Error message

/text-to-speech cue {cue.index}: status={resp.status_code}, body={resp.text}

What it means

RuntimeError raised in _noiz_tts when the per-cue /text-to-speech request returns non-200. The cue index and response body identify which subtitle line failed and why, so a multi-cue render pinpoints the failure.

Source

Thrown at skills/speak/scripts/render_timeline.py:298

        }
    elif not cfg.get("voice_id"):
        raise ValueError(
            f"Cue {cue.index}: either voice_id or reference_audio required."
        )

    try:
        resp = requests.post(
            url, headers={"Authorization": api_key},
            data=payload, files=files, timeout=timeout,
        )
    finally:
        if files and files["file"][1]:
            files["file"][1].close()
        if ref_cleanup is not None:
            ref_cleanup.unlink(missing_ok=True)

    if resp.status_code != 200:
        raise RuntimeError(
            f"/text-to-speech cue {cue.index}: "
            f"status={resp.status_code}, body={resp.text}"
        )
    out_path.write_bytes(resp.content)
    dur_h = resp.headers.get("X-Audio-Duration")
    return float(dur_h) if dur_h else -1.0


# ── Kokoro backend ───────────────────────────────────────────────────


def _ensure_kokoro() -> None:
    if not shutil.which("kokoro-tts"):
        raise RuntimeError("kokoro-tts CLI not found.")


def _kokoro_tts(
    cue: Cue,

View on GitHub (pinned to 28dc5e14f1)

Solutions

  1. Use the cue index in the message to find the offending subtitle line
  2. Apply the body's guidance: shorten text, fix voice config, or back off for 429
  3. Add per-cue retry with backoff
  4. Cache successful cues so reruns skip completed ones

Example fix

# before
seg = _noiz_tts(cue, cfg, fmt, ...)
# after
for attempt in range(3):
    try:
        seg = _noiz_tts(cue, cfg, fmt, ...)
        break
    except RuntimeError:
        if attempt == 2: raise
        time.sleep(2 ** attempt)
Defensive patterns

Strategy: retry

Validate before calling

assert len(cue.text) <= 5000, f'cue {cue.index} too long'

Try / catch

for attempt in range(3):
    try:
        seg = _noiz_tts(cue, cfg, fmt, ...); break
    except RuntimeError as e:
        if attempt == 2 or 'status=4' in str(e) and '429' not in str(e): raise
        time.sleep(2 ** attempt)

Prevention

When it happens

Trigger: A cue with text over the char limit, invalid voice_id/reference for that cue, auth failure, or rate limiting after many sequential requests.

Common situations: One overly long subtitle line, a voice-map entry with an expired/invalid voice id, or 429s during a large timeline render.

Related errors


AI-assisted analysis of babysor/MockingBird@28dc5e14f1 (2026-08-27). Data as JSON: /api/errors/aff024bf2abc0f97. Report an issue: GitHub.