jamiepine/voicebox · error · ValueError

Cloned profiles cannot set preset_engine or preset_voice_id

Error message

Cloned profiles cannot set preset_engine or preset_voice_id

What it means

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.

Source

Thrown at backend/services/profiles.py:173

    existing_profile = db.query(DBVoiceProfile).filter_by(name=data.name).first()
    if existing_profile:
        raise ValueError(f"A profile with the name '{data.name}' already exists. Please choose a different name.")

    # Auto-set default_engine for preset profiles
    default_engine = data.default_engine
    voice_type = data.voice_type or "cloned"
    if voice_type == "preset" and data.preset_engine and not default_engine:
        default_engine = data.preset_engine

    validation_error = _validate_profile_fields(
        voice_type=voice_type,
        preset_engine=data.preset_engine,
        preset_voice_id=data.preset_voice_id,
        design_prompt=data.design_prompt,
        default_engine=default_engine,
    )
    if validation_error:
        raise ValueError(validation_error)

    db_profile = DBVoiceProfile(
        id=str(uuid.uuid4()),
        name=data.name,
        description=data.description,
        language=data.language,
        voice_type=voice_type,
        preset_engine=data.preset_engine,
        preset_voice_id=data.preset_voice_id,
        design_prompt=data.design_prompt,
        default_engine=default_engine,
        personality=data.personality,
        created_at=datetime.utcnow(),
        updated_at=datetime.utcnow(),
    )

    db.add(db_profile)
    db.commit()

View on GitHub (pinned to 51f49dea19)

Solutions

  1. If you intend a preset voice, set voice_type:'preset' (and supply both preset fields).
  2. If you intend a cloned voice, remove preset_engine and preset_voice_id from the payload.
  3. Make the client set voice_type explicitly rather than relying on the 'cloned' default.

Example fix

// before
{"name":"Echo","preset_engine":"kokoro","preset_voice_id":"af_heart"}
// after - explicitly a preset profile
{"name":"Echo","voice_type":"preset","preset_engine":"kokoro","preset_voice_id":"af_heart"}
Defensive patterns

Strategy: validation

Validate before calling

def cloned_has_no_preset_fields(data) -> bool:
    vt = data.voice_type or "cloned"
    return vt != "cloned" or (not data.preset_engine and not data.preset_voice_id)

if not cloned_has_no_preset_fields(data):
    raise HTTPException(400, "cloned profiles must not include preset_engine/preset_voice_id")

Type guard

def is_clean_cloned(data) -> bool:
    vt = getattr(data, "voice_type", None) or "cloned"
    return vt != "cloned" or (
        not getattr(data, "preset_engine", None) and not getattr(data, "preset_voice_id", None)
    )

Prevention

When it happens

Trigger: POSTing a profile with voice_type omitted (defaults to 'cloned') or set to 'cloned', while also including preset_engine or preset_voice_id.

Common situations: 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'.

Related errors


AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12). Data as JSON: /api/errors/f2aad0cbde07c62f. Report an issue: GitHub.