jamiepine/voicebox · error · ValueError

No voice profile resolved. Pass `profile=` with a voice prof

Error message

No voice profile resolved. Pass `profile=` with a voice profile name or id, or set a default voice in Voicebox → Settings → MCP.

What it means

Raised by the voicebox.speak MCP tool when resolve_profile returns None. Resolution falls through three layers — explicit profile arg, MCPClientBinding.profile_id for the calling client, and CaptureSettings.default_playback_voice_id (global default) — and none of them yielded a VoiceProfile row.

Source

Thrown at backend/mcp_server/tools.py:79

        prompt — when true, the text is first rewritten in character by the
        LLM before TTS. When omitted, the per-client binding's
        ``default_personality`` flag decides; when that is unset, the
        default is plain TTS.

        ``model_size`` selects a model variant for engines that ship more
        than one — ``qwen`` and ``qwen_custom_voice`` accept "1.7B" (default)
        or "0.6B"; ``tada`` accepts "1B" or "3B". Other engines ignore it.
        Omit to use the engine default. Requesting a smaller variant (e.g.
        "0.6B") is faster and avoids reloading a heavier model between calls.
        """
        from ..database.models import MCPClientBinding

        db = next(get_db())
        try:
            client_id = current_client_id.get()
            vp = resolve_profile(profile, client_id, db)
            if vp is None:
                raise ValueError(
                    "No voice profile resolved. Pass `profile=` with a "
                    "voice profile name or id, or set a default voice in "
                    "Voicebox → Settings → MCP."
                )

            binding = None
            if client_id:
                binding = (
                    db.query(MCPClientBinding)
                    .filter(MCPClientBinding.client_id == client_id)
                    .first()
                )

            resolved_personality = personality
            if resolved_personality is None and binding is not None:
                resolved_personality = bool(binding.default_personality)

            resolved_engine = engine

View on GitHub (pinned to 51f49dea19)

Solutions

  1. Pass profile="<name-or-id>" explicitly on the tool call with an existing VoiceProfile.
  2. Open Voicebox → Settings → MCP and set a default playback voice (writes CaptureSettings.default_playback_voice_id).
  3. Bind the MCP client to a profile via MCPClientBinding (per-client override).
  4. Verify the profile still exists in the DB — a deleted profile name resolves to None.

Example fix

// before
voicebox.speak(text="hello")
// after
voicebox.speak(text="hello", profile="Ryan")
Defensive patterns

Strategy: validation

Validate before calling

from backend.services.profiles import get_profile_orm_by_name_or_id
from backend.database import get_db

db = next(get_db())
if not profile_arg:
    # ensure a default exists before calling speak
    from backend.database.models import CaptureSettings
    has_default = db.query(CaptureSettings).filter(CaptureSettings.id == 1).first()
    if not has_default or not has_default.default_playback_voice_id:
        raise RuntimeError("no default voice set; configure Voicebox → Settings → MCP")
elif get_profile_orm_by_name_or_id(profile_arg, db) is None:
    raise ValueError(f"profile {profile_arg!r} not found")
# safe to call voicebox.speak

Type guard

def profile_is_resolvable(profile_arg: str | None, client_id: str | None) -> bool:
    from backend.mcp_server.resolve import resolve_profile
    from backend.database import get_db
    db = next(get_db())
    return resolve_profile(profile_arg, client_id, db) is not None

Try / catch

try:
    await voicebox.speak(text=text, profile=profile_arg)
except ValueError as exc:
    if "No voice profile resolved" in str(exc):
        # set a fallback default or prompt the user to configure one
        await voicebox.speak(text=text, profile="Ryan")
    else:
        raise

Prevention

When it happens

Trigger: Calling voicebox.speak without profile= on a client that has no MCPClientBinding and no global default voice; passing a profile= name or id that does not exist in the DB; a fresh install where no default playback voice has been set.

Common situations: New deployment before any voice profile is configured; client_id changed so the per-client binding no longer matches; the named profile was deleted but a stale reference remains; the user expects an implicit default that was never created.

Related errors


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