{"record":{"id":"6e301fd7be1fbc0c","repo":"jamiepine/voicebox","slug":"no-voice-profile-resolved-pass-profile-with-a","errorCode":null,"errorMessage":"No voice profile resolved. Pass `profile=` with a voice profile name or id, or set a default voice in Voicebox → Settings → MCP.","messagePattern":"No voice profile resolved\\. Pass `profile=` with a voice profile name or id, or set a default voice in Voicebox → Settings → MCP\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"backend/mcp_server/tools.py","lineNumber":79,"sourceCode":"        prompt — when true, the text is first rewritten in character by the\n        LLM before TTS. When omitted, the per-client binding's\n        ``default_personality`` flag decides; when that is unset, the\n        default is plain TTS.\n\n        ``model_size`` selects a model variant for engines that ship more\n        than one — ``qwen`` and ``qwen_custom_voice`` accept \"1.7B\" (default)\n        or \"0.6B\"; ``tada`` accepts \"1B\" or \"3B\". Other engines ignore it.\n        Omit to use the engine default. Requesting a smaller variant (e.g.\n        \"0.6B\") is faster and avoids reloading a heavier model between calls.\n        \"\"\"\n        from ..database.models import MCPClientBinding\n\n        db = next(get_db())\n        try:\n            client_id = current_client_id.get()\n            vp = resolve_profile(profile, client_id, db)\n            if vp is None:\n                raise ValueError(\n                    \"No voice profile resolved. Pass `profile=` with a \"\n                    \"voice profile name or id, or set a default voice in \"\n                    \"Voicebox → Settings → MCP.\"\n                )\n\n            binding = None\n            if client_id:\n                binding = (\n                    db.query(MCPClientBinding)\n                    .filter(MCPClientBinding.client_id == client_id)\n                    .first()\n                )\n\n            resolved_personality = personality\n            if resolved_personality is None and binding is not None:\n                resolved_personality = bool(binding.default_personality)\n\n            resolved_engine = engine","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/mcp_server/tools.py#L61-L97","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pass profile=\"<name-or-id>\" explicitly on the tool call with an existing VoiceProfile.","Open Voicebox → Settings → MCP and set a default playback voice (writes CaptureSettings.default_playback_voice_id).","Bind the MCP client to a profile via MCPClientBinding (per-client override).","Verify the profile still exists in the DB — a deleted profile name resolves to None."],"exampleFix":"// before\nvoicebox.speak(text=\"hello\")\n// after\nvoicebox.speak(text=\"hello\", profile=\"Ryan\")","handlingStrategy":"validation","validationCode":"from backend.services.profiles import get_profile_orm_by_name_or_id\nfrom backend.database import get_db\n\ndb = next(get_db())\nif not profile_arg:\n    # ensure a default exists before calling speak\n    from backend.database.models import CaptureSettings\n    has_default = db.query(CaptureSettings).filter(CaptureSettings.id == 1).first()\n    if not has_default or not has_default.default_playback_voice_id:\n        raise RuntimeError(\"no default voice set; configure Voicebox → Settings → MCP\")\nelif get_profile_orm_by_name_or_id(profile_arg, db) is None:\n    raise ValueError(f\"profile {profile_arg!r} not found\")\n# safe to call voicebox.speak","typeGuard":"def profile_is_resolvable(profile_arg: str | None, client_id: str | None) -> bool:\n    from backend.mcp_server.resolve import resolve_profile\n    from backend.database import get_db\n    db = next(get_db())\n    return resolve_profile(profile_arg, client_id, db) is not None","tryCatchPattern":"try:\n    await voicebox.speak(text=text, profile=profile_arg)\nexcept ValueError as exc:\n    if \"No voice profile resolved\" in str(exc):\n        # set a fallback default or prompt the user to configure one\n        await voicebox.speak(text=text, profile=\"Ryan\")\n    else:\n        raise","preventionTips":["During install/setup, always create a VoiceProfile and set CaptureSettings.default_playback_voice_id.","Pass profile= explicitly from MCP clients rather than relying on resolution chains.","Verify the profile name still exists before forwarding (deleted profiles resolve to None)."],"tags":["mcp","voice-profile","configuration","qwen-tts"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}