{"record":{"id":"3368c96a4c5f88a4","repo":"jamiepine/voicebox","slug":"preset-profile-profile-id-is-missing-preset-engi","errorCode":null,"errorMessage":"Preset profile {profile.id} is missing preset engine metadata","messagePattern":"Preset profile (.+?) is missing preset engine metadata","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"backend/services/profiles.py","lineNumber":121,"sourceCode":"        return None\n\n    if preset_engine or preset_voice_id:\n        return \"Cloned profiles cannot set preset_engine or preset_voice_id\"\n    if design_prompt:\n        return \"Cloned profiles cannot set design_prompt\"\n    if default_engine and default_engine not in CLONING_ENGINES:\n        return f\"Cloned profiles cannot use default engine '{default_engine}'\"\n    return None\n\n\ndef validate_profile_engine(profile, engine: str) -> None:\n    voice_type = getattr(profile, \"voice_type\", None) or \"cloned\"\n\n    if voice_type == \"preset\":\n        preset_engine = getattr(profile, \"preset_engine\", None)\n        preset_voice_id = getattr(profile, \"preset_voice_id\", None)\n        if not preset_engine or not preset_voice_id:\n            raise ValueError(f\"Preset profile {profile.id} is missing preset engine metadata\")\n        if preset_engine != engine:\n            raise ValueError(\n                f\"Preset profile {profile.id} only supports engine '{preset_engine}', not '{engine}'\"\n            )\n        return\n\n    if voice_type == \"designed\":\n        design_prompt = getattr(profile, \"design_prompt\", None)\n        if not design_prompt or not design_prompt.strip():\n            raise ValueError(f\"Designed profile {profile.id} is missing design_prompt\")\n        return\n\n    if engine not in CLONING_ENGINES:\n        raise ValueError(f\"Engine '{engine}' does not support cloned voice profiles\")\n\n\nasync def create_profile(\n    data: VoiceProfileCreate,","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/services/profiles.py#L103-L139","documentation":"Raised by validate_profile_engine when profile.voice_type == 'preset' but either preset_engine or preset_voice_id is unset (profiles.py:117-121). A preset profile is identified by these two fields — a preset row missing them is malformed and cannot be used with any engine. This is a data-integrity failure, not a misuse of the API.","triggerScenarios":"A profile row was inserted with voice_type='preset' but preset_engine/preset_voice_id left null (manual SQL, a migration that set voice_type without backfilling the metadata, or a bug in create_profile).","commonSituations":"Schema migration introduced voice_type and classified some rows as 'preset' without populating the engine fields; an admin edited the row directly; a copy-paste between profiles dropped the fields.","solutions":["Backfill the missing fields: UPDATE voice_profiles SET preset_engine='<engine>', preset_voice_id='<vid>' WHERE id='<id>';.","If the row is not actually a preset, set voice_type to 'cloned' or 'designed' as appropriate and clear the preset fields.","Audit the create_profile path to confirm preset_engine and preset_voice_id are required when voice_type='preset'.","Add a DB-level CHECK constraint so this cannot recur."],"exampleFix":"-- before: preset row missing metadata\nSELECT voice_type, preset_engine, preset_voice_id FROM voice_profiles WHERE id='<id>';\n-- voice_type=preset, preset_engine=NULL\n-- after\nUPDATE voice_profiles SET preset_engine='qwen', preset_voice_id='voice-1' WHERE id='<id>';","handlingStrategy":"type-guard","validationCode":"def preset_profile_complete(profile) -> bool:\n    return (\n        getattr(profile, 'voice_type', None) == 'preset'\n        and bool(getattr(profile, 'preset_engine', None))\n        and bool(getattr(profile, 'preset_voice_id', None))\n    )\n\n# before validate_profile_engine:\nif profile.voice_type == 'preset' and not preset_profile_complete(profile):\n    raise HTTPException(409, f'Profile {profile.id} is malformed: preset metadata missing.')","typeGuard":"def is_complete_preset(profile) -> bool:\n    return (\n        getattr(profile, 'voice_type', None) == 'preset'\n        and isinstance(getattr(profile, 'preset_engine', None), str) and bool(profile.preset_engine)\n        and isinstance(getattr(profile, 'preset_voice_id', None), str) and bool(profile.preset_voice_id)\n    )","tryCatchPattern":"try:\n    validate_profile_engine(profile, engine)\nexcept ValueError as e:\n    if 'missing preset engine metadata' in str(e):\n        raise HTTPException(409, str(e))  # data integrity, not user error\n    raise HTTPException(400, str(e))","preventionTips":["Enforce preset_engine + preset_voice_id at create time when voice_type='preset'.","Add a DB CHECK constraint so the combo cannot be partial.","Audit rows after any migration that touches voice_type."],"tags":["profile","preset","validation","data-integrity"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}