babysor/MockingBird · error · RuntimeError

/emotion-enhance failed: status={resp.status_code}, body={re

Error message

/emotion-enhance failed: status={resp.status_code}, body={resp.text}

What it means

Same check as noiz_tts.call_emotion_enhance but in render_timeline's _noiz_emotion_enhance: the Noiz /emotion-enhance endpoint returned a non-200 status and the body is surfaced for diagnosis.

Source

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

    _run_ff(cmd)


# ── Noiz backend ─────────────────────────────────────────────────────


def _noiz_emotion_enhance(
    base_url: str, api_key: str, text: str, timeout: int
) -> str:
    import requests  # noqa: delayed import so kokoro path doesn't need requests

    resp = requests.post(
        f"{base_url.rstrip('/')}/emotion-enhance",
        headers={"Authorization": api_key, "Content-Type": "application/json"},
        json={"text": text},
        timeout=timeout,
    )
    if resp.status_code != 200:
        raise RuntimeError(
            f"/emotion-enhance failed: status={resp.status_code}, body={resp.text}"
        )
    enhanced = resp.json().get("data", {}).get("emotion_enhance")
    if not enhanced:
        raise RuntimeError(f"/emotion-enhance returned no data: {resp.text}")
    return enhanced


def _bool_form(v: Any) -> str:
    return "true" if bool(v) else "false"


def _resolve_reference_audio(ref: str, timeout: int) -> Tuple[Path, Optional[Path]]:
    """Resolve reference_audio to a path. If ref is a URL, download to temp file.
    Returns (path_to_use, temp_path_to_cleanup_or_None)."""
    if ref.startswith("http://") or ref.startswith("https://"):
        import requests
        tmp = tempfile.NamedTemporaryFile(suffix=".wav", delete=False)

View on GitHub (pinned to 28dc5e14f1)

Solutions

  1. Add a delay/backoff between per-cue requests to avoid 429
  2. Check credentials and base_url from the message body
  3. Retry the single failing cue rather than the whole render
  4. Skip emotion enhancement (feature flag) if non-essential

Example fix

# before
enhanced = _noiz_emotion_enhance(base_url, key, cue.text, timeout)
# after
try:
    enhanced = _noiz_emotion_enhance(base_url, key, cue.text, timeout)
except RuntimeError:
    time.sleep(2)
    enhanced = _noiz_emotion_enhance(base_url, key, cue.text, timeout)
Defensive patterns

Strategy: retry

Validate before calling

r = requests.get(f"{base_url}/health", headers={"Authorization": key}, timeout=5)
assert r.ok

Try / catch

for attempt in range(3):
    try:
        return _noiz_emotion_enhance(base_url, key, cue.text, timeout)
    except RuntimeError:
        time.sleep(2 ** attempt)
raise

Prevention

When it happens

Trigger: Per-cue emotion enhancement POST failing with 401 (bad key), 429 (rate limit from many cues in a row), 400, or 5xx.

Common situations: Rate limiting when enhancing a long timeline of cues rapidly, expired credentials, or service outages mid-render.

Related errors


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