{"record":{"id":"dc80c09759435b48","repo":"jamiepine/voicebox","slug":"preset-profiles-require-both-preset-engine-and-pre","errorCode":null,"errorMessage":"Preset profiles require both preset_engine and preset_voice_id","messagePattern":"Preset profiles require both preset_engine and preset_voice_id","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' but preset_engine or preset_voice_id is falsy. A preset profile is an engine-native voice (kokoro/qwen_custom_voice), so both identifiers are mandatory; one without the other is meaningless. Fires during create_profile before any DB write.","triggerScenarios":"POSTing {voice_type:'preset'} with preset_engine omitted, preset_voice_id omitted, or either set to empty string. Note create_profile only auto-fills default_engine from preset_engine; it never fills in a missing voice_id.","commonSituations":"Frontend exposes a voice picker but the engine/voice selection is sent as two separate fields and one is left blank; client defaulting voice_type to 'preset' while only sending preset_engine.","solutions":["Send both preset_engine and preset_voice_id together, e.g. {voice_type:'preset', preset_engine:'kokoro', preset_voice_id:'af_heart'}.","If you do not have a specific preset voice, use voice_type:'cloned' (default) instead.","Validate the pair in the client before submit and disable the create button until both are set."],"exampleFix":"// before\n{\"name\":\"Announcer\",\"voice_type\":\"preset\",\"preset_engine\":\"kokoro\"}\n// after\n{\"name\":\"Announcer\",\"voice_type\":\"preset\",\"preset_engine\":\"kokoro\",\"preset_voice_id\":\"af_heart\"}","handlingStrategy":"validation","validationCode":"def is_valid_preset_payload(data) -> bool:\n    return (\n        data.voice_type == \"preset\"\n        and bool(data.preset_engine)\n        and bool(data.preset_voice_id)\n    )\n\nif data.voice_type == \"preset\" and not is_valid_preset_payload(data):\n    raise HTTPException(400, \"preset profiles need both preset_engine and preset_voice_id\")","typeGuard":"def is_complete_preset(data) -> bool:\n    return (\n        getattr(data, \"voice_type\", None) == \"preset\"\n        and bool(getattr(data, \"preset_engine\", None))\n        and bool(getattr(data, \"preset_voice_id\", None))\n    )","tryCatchPattern":null,"preventionTips":["In the UI, only enable 'create' for preset profiles once both engine and voice are chosen.","Keep preset_engine and preset_voice_id in a single composite selection so one cannot be sent without the other.","Run the same _validate_profile_fields check client-side before submit."],"tags":["profiles","preset","validation","create","required-field"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}