{"record":{"id":"f11d88ab82a80d28","repo":"jamiepine/voicebox","slug":"preset-profile-profile-id-only-supports-engine","errorCode":null,"errorMessage":"Preset profile {profile.id} only supports engine '{preset_engine}', not '{engine}'","messagePattern":"Preset profile (.+?) only supports engine '(.+?)', not '(.+?)'","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"backend/services/profiles.py","lineNumber":123,"sourceCode":"    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,\n    db: Session,\n) -> VoiceProfileResponse:","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/services/profiles.py#L105-L141","documentation":"Raised by validate_profile_engine when a preset profile's preset_engine does not match the engine argument passed to the call (profiles.py:122-125). Preset profiles are pinned to one engine — the engine chosen when the preset was bound — so a generate/speak call that requests a different engine is rejected rather than silently producing output on the wrong engine.","triggerScenarios":"POST /generate with engine='chatterbox' against a profile whose preset_engine='qwen'; client default engine differs from the engine used to create the preset; a session-level engine override conflicts with the preset's binding.","commonSituations":"Frontend has a global engine selector that the user changed after selecting a preset voice; API consumer hard-coded an engine string without reading the profile's preset_engine; engine names changed across versions (e.g. 'chatterbox' vs 'chatterbox_turbo').","solutions":["Read profile.preset_engine and pass that exact value as the engine argument.","If the UI exposes an engine selector, disable it (or auto-set it) when a preset profile is selected.","If the engine names changed across versions, migrate preset_engine values to the new names.","If you genuinely need a different engine, clone the voice into a non-preset profile first."],"exampleFix":"# before\nawait generate(profile, engine='chatterbox')  # profile.preset_engine='qwen'\n# after\nawait generate(profile, engine=profile.preset_engine)","handlingStrategy":"validation","validationCode":"def engine_matches_preset(profile, engine: str) -> bool:\n    return getattr(profile, 'preset_engine', None) == engine\n\n# before generate:\nif profile.voice_type == 'preset' and not engine_matches_preset(profile, engine):\n    raise HTTPException(409, f\"This preset profile only supports engine '{profile.preset_engine}'.\")","typeGuard":"def is_compatible_engine(profile, engine: str) -> bool:\n    vt = getattr(profile, 'voice_type', None) or 'cloned'\n    if vt == 'preset':\n        return getattr(profile, 'preset_engine', None) == engine\n    return True","tryCatchPattern":"try:\n    validate_profile_engine(profile, engine)\nexcept ValueError as e:\n    if 'only supports engine' in str(e):\n        raise HTTPException(409, str(e))\n    raise HTTPException(400, str(e))","preventionTips":["Auto-set the engine selector from profile.preset_engine when a preset is chosen.","Disable the engine dropdown in the UI for preset profiles.","Normalize engine strings (lowercase, strip) before comparison."],"tags":["profile","preset","engine","validation"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}