{"record":{"id":"2ae032af1c915c88","repo":"jamiepine/voicebox","slug":"preset-voice-preset-voice-id-is-not-valid-for","errorCode":null,"errorMessage":"Preset voice '{preset_voice_id}' is not valid for engine '{preset_engine}'","messagePattern":"Preset voice '(.+?)' is not valid for engine '(.+?)'","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"backend/services/profiles.py","lineNumber":173,"sourceCode":"    existing_profile = db.query(DBVoiceProfile).filter_by(name=data.name).first()\n    if existing_profile:\n        raise ValueError(f\"A profile with the name '{data.name}' already exists. Please choose a different name.\")\n\n    # Auto-set default_engine for preset profiles\n    default_engine = data.default_engine\n    voice_type = data.voice_type or \"cloned\"\n    if voice_type == \"preset\" and data.preset_engine and not default_engine:\n        default_engine = data.preset_engine\n\n    validation_error = _validate_profile_fields(\n        voice_type=voice_type,\n        preset_engine=data.preset_engine,\n        preset_voice_id=data.preset_voice_id,\n        design_prompt=data.design_prompt,\n        default_engine=default_engine,\n    )\n    if validation_error:\n        raise ValueError(validation_error)\n\n    db_profile = DBVoiceProfile(\n        id=str(uuid.uuid4()),\n        name=data.name,\n        description=data.description,\n        language=data.language,\n        voice_type=voice_type,\n        preset_engine=data.preset_engine,\n        preset_voice_id=data.preset_voice_id,\n        design_prompt=data.design_prompt,\n        default_engine=default_engine,\n        personality=data.personality,\n        created_at=datetime.utcnow(),\n        updated_at=datetime.utcnow(),\n    )\n\n    db.add(db_profile)\n    db.commit()","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/services/profiles.py#L155-L191","documentation":"Returned by _validate_profile_fields when voice_type=='preset' and preset_voice_id is not in the known voice set for preset_engine. Only engines 'kokoro' (KOKORO_VOICES) and 'qwen_custom_voice' (QWEN_CUSTOM_VOICES) publish voice lists; for any other preset_engine the set is empty and this check is skipped, so this error only fires for those two engines.","triggerScenarios":"POSTing preset_engine:'kokoro' with a voice id not in KOKORO_VOICES, or preset_engine:'qwen_custom_voice' with an id not in QWEN_CUSTOM_VOICES. A typo, a stale id from an older backend version, or an id belonging to a different engine.","commonSituations":"Hardcoding a voice id that was renamed/removed in a backend update; mixing up ids across engines (e.g. sending a kokoro id under qwen_custom_voice); frontend dropdown populated from a cached/stale list.","solutions":["Fetch the live voice list for the engine (KOKORO_VOICES / QWEN_CUSTOM_VOICES) and select a valid preset_voice_id from it.","Upgrade the backend so the voice catalogue matches what the client expects, then re-send.","Regenerate the client's voice picker from the current backend response, not a cached snapshot."],"exampleFix":"// before\n{\"voice_type\":\"preset\",\"preset_engine\":\"kokoro\",\"preset_voice_id\":\"af_heart_x\"}\n// after - use a real kokoro voice id\n{\"voice_type\":\"preset\",\"preset_engine\":\"kokoro\",\"preset_voice_id\":\"af_heart\"}","handlingStrategy":"validation","validationCode":"from ..backends.kokoro_backend import KOKORO_VOICES\nfrom ..backends.qwen_custom_voice_backend import QWEN_CUSTOM_VOICES\n\ndef known_voice_ids(engine: str) -> set[str]:\n    if engine == \"kokoro\":\n        return {v for v, *_ in KOKORO_VOICES}\n    if engine == \"qwen_custom_voice\":\n        return {v for v, *_ in QWEN_CUSTOM_VOICES}\n    return set()  # other engines: catalogue not enforced\n\ndef preset_voice_ok(engine: str, voice_id: str) -> bool:\n    ids = known_voice_ids(engine)\n    return (not ids) or (voice_id in ids)","typeGuard":"def is_known_preset_voice(engine: str, voice_id: str) -> bool:\n    ids = known_voice_ids(engine)\n    return not ids or voice_id in ids","tryCatchPattern":null,"preventionTips":["Populate the voice picker from the live KOKORO_VOICES / QWEN_CUSTOM_VOICES rather than a hardcoded list.","Pin the backend version so the voice catalogue doesn't drift under your client.","On backend upgrade, diff the voice catalogue and update any stored preset profiles whose ids disappeared."],"tags":["profiles","preset","validation","create","invalid-voice-id","kokoro","qwen-custom-voice"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}