jamiepine/voicebox · error · HTTPException

{exception message from delete_preset (ValueError)}

Error message

{exception message from delete_preset (ValueError)}

What it means

Returned as HTTP 400 by DELETE /effects/presets/{preset_id}. effects.delete_preset() raises ValueError("Cannot delete built-in presets") when the looked-up row has is_builtin=True. The route catches ValueError and surfaces str(e) as the detail. Built-in presets are seed data shipped with the app and are intentionally immutable.

Source

Thrown at backend/routes/effects.py:135

        result = effects_mod.update_preset(preset_id, data, db)
        if not result:
            raise HTTPException(status_code=404, detail="Preset not found")
        return result
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))


@router.delete("/effects/presets/{preset_id}")
async def delete_effect_preset(preset_id: str, db: Session = Depends(get_db)):
    """Delete a user effect preset."""
    from ..services import effects as effects_mod

    try:
        if not effects_mod.delete_preset(preset_id, db):
            raise HTTPException(status_code=404, detail="Preset not found")
        return {"status": "deleted"}
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))


@router.get(
    "/generations/{generation_id}/versions",
    response_model=list[models.GenerationVersionResponse],
)
async def list_generation_versions(
    generation_id: str,
    db: Session = Depends(get_db),
):
    """List all versions for a generation."""
    gen = await history.get_generation(generation_id, db)
    if not gen:
        raise HTTPException(status_code=404, detail="Generation not found")

    from ..services import versions as versions_mod

    return versions_mod.list_versions(generation_id, db)

View on GitHub (pinned to 51f49dea19)

Solutions

  1. Filter the preset list by is_builtin === false before offering delete in the UI.
  2. If bulk cleanup is needed, only delete user presets and treat built-ins as read-only reference data.
  3. Add a client-side guard: if (preset.is_builtin) return; before calling delete.
  4. Re-seed built-ins via the app's seeding path rather than deleting and recreating them.

Example fix

// before
for (const p of allPresets) await api.delete(`/effects/presets/${p.id}`);
// after
for (const p of allPresets) {
  if (p.is_builtin) continue; // built-ins are immutable
  await api.delete(`/effects/presets/${p.id}`);
}
Defensive patterns

Strategy: validation

Validate before calling

# Never attempt to delete built-in presets.
preset = next((p for p in effects_mod.list_presets(db) if p.id == preset_id), None)
if preset is None:
    return  # will 404 instead
if preset.is_builtin:
    return  # would 400 — skip; built-ins are immutable
# safe to delete

Type guard

def is_user_preset(preset) -> bool:
    """True only for a deletable, user-created preset."""
    return preset is not None and not bool(preset.is_builtin)

Try / catch

try:
    client.delete(f'/effects/presets/{preset_id}')
except HTTPStatusError as e:
    if e.response.status_code == 400 and 'built-in' in e.response.json()['detail']:
        # expected when hitting a built-in — filter these out upstream
        return
    raise

Prevention

When it happens

Trigger: DELETE /effects/presets/{preset_id} where preset_id resolves to a row with is_builtin=True; iterating over GET /effects/presets and calling delete on every entry including built-ins; UI failing to hide/disable the delete button for built-in presets.

Common situations: A 'reset to defaults' feature that tries to wipe all presets; bulk-delete scripts that do not filter on is_builtin; frontend that does not read the is_builtin flag on EffectPresetResponse; running a destructive migration without guarding built-ins.

Related errors


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