{"record":{"id":"f2aad0cbde07c62f","repo":"jamiepine/voicebox","slug":"cloned-profiles-cannot-set-preset-engine-or-preset","errorCode":null,"errorMessage":"Cloned profiles cannot set preset_engine or preset_voice_id","messagePattern":"Cloned profiles cannot set preset_engine or 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 for the default (cloned) branch when preset_engine or preset_voice_id is set. A cloned profile is built from uploaded audio samples; preset identifiers belong to the 'preset' voice_type and are not allowed here.","triggerScenarios":"POSTing a profile with voice_type omitted (defaults to 'cloned') or set to 'cloned', while also including preset_engine or preset_voice_id.","commonSituations":"Client defaulting voice_type to None (=cloned) but always sending preset_engine from a global config; reusing a preset profile payload and forgetting to set voice_type:'preset'.","solutions":["If you intend a preset voice, set voice_type:'preset' (and supply both preset fields).","If you intend a cloned voice, remove preset_engine and preset_voice_id from the payload.","Make the client set voice_type explicitly rather than relying on the 'cloned' default."],"exampleFix":"// before\n{\"name\":\"Echo\",\"preset_engine\":\"kokoro\",\"preset_voice_id\":\"af_heart\"}\n// after - explicitly a preset profile\n{\"name\":\"Echo\",\"voice_type\":\"preset\",\"preset_engine\":\"kokoro\",\"preset_voice_id\":\"af_heart\"}","handlingStrategy":"validation","validationCode":"def cloned_has_no_preset_fields(data) -> bool:\n    vt = data.voice_type or \"cloned\"\n    return vt != \"cloned\" or (not data.preset_engine and not data.preset_voice_id)\n\nif not cloned_has_no_preset_fields(data):\n    raise HTTPException(400, \"cloned profiles must not include preset_engine/preset_voice_id\")","typeGuard":"def is_clean_cloned(data) -> bool:\n    vt = getattr(data, \"voice_type\", None) or \"cloned\"\n    return vt != \"cloned\" or (\n        not getattr(data, \"preset_engine\", None) and not getattr(data, \"preset_voice_id\", None)\n    )","tryCatchPattern":null,"preventionTips":["Always set voice_type explicitly so an implicit 'cloned' never surprises you with preset fields.","Strip preset fields from the payload when voice_type is cloned.","Use distinct request forms per voice_type in the client."],"tags":["profiles","cloned","validation","create","mutually-exclusive"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}