unslothai/unsloth · warning · HTTPException

str(exc)

Error message

str(exc)

What it means

HTTP 409 from PUT /settings/generation-presets/{image|video}/custom. The route calls upsert_media_generation_preset and converts any ValueError into 409 with str(exc). The stored-preset store raises ValueError('Delete a preset before saving another one') when you add a NEW preset name while the readable-preset slots are already at the maximum count (_MAX_PRESETS). The lambda also re-validates that each stored preset round-trips through the current pydantic schema; unrelated schema failures surface as other errors.

Source

Thrown at studio/backend/routes/settings.py:430

        lambda stored, submitted: _preserve_recovered_defaults(
            VideoGenerationPresetState, stored, submitted
        ),
    )
    return {"saved": True}


def _upsert_custom_generation_preset(
    kind: Literal["image", "video"], payload: ImageGenerationPreset | VideoGenerationPreset
) -> dict[str, bool]:
    try:
        schema = type(payload)
        upsert_media_generation_preset(
            kind,
            payload.model_dump(),
            lambda stored: _validated_readable_model(schema, stored) is not None,
        )
    except ValueError as exc:
        raise HTTPException(status_code = 409, detail = str(exc)) from exc
    return {"saved": True}


@router.put("/generation-presets/image/custom")
def upsert_custom_image_generation_preset(
    payload: ImageGenerationPreset, current_subject: str = Depends(get_current_subject)
) -> dict[str, bool]:
    return _upsert_custom_generation_preset("image", payload)


@router.put("/generation-presets/video/custom")
def upsert_custom_video_generation_preset(
    payload: VideoGenerationPreset, current_subject: str = Depends(get_current_subject)
) -> dict[str, bool]:
    return _upsert_custom_generation_preset("video", payload)


@router.delete("/generation-presets/{kind}/custom")

View on GitHub (pinned to 203007d190)

Solutions

  1. DELETE /settings/generation-presets/{kind}/custom?name=<old> for a preset you no longer need, then retry the PUT.
  2. Overwrite an existing preset instead of adding a new one — same name replaces in place and never hits the cap.
  3. Show the preset count/limit in the UI and disable 'Save as new' when full.

Example fix

// before
await api.put('/settings/generation-presets/image/custom', { name: `Style ${n+1}`, params }); // 409 cap

// after
if (presets.length >= MAX_PRESETS) {
  await api.delete(`/settings/generation-presets/image/custom?name=${encodeURIComponent(oldest)}`);
}
await api.put('/settings/generation-presets/image/custom', { name: `Style ${n+1}`, params });
Defensive patterns

Strategy: validation

Validate before calling

const existing = await api.getCustomPresets(kind);
const isReplace = existing.some(p => p.name === payload.name);
if (!isReplace && existing.length >= MAX_PRESETS) {
  throw new Error('Preset limit reached — delete one first');
}
await api.put(`/settings/generation-presets/${kind}/custom`, payload);

Type guard

function canSavePreset(list: { name: string }[], name: string, max: number): boolean {
  return list.some(p => p.name === name) || list.length < max;
}

Try / catch

try { await api.put(presetUrl, payload); }
catch (e) {
  if (e.status === 409 && /Delete a preset/.test(e.detail)) { promptDeleteOldest(); return; }
  throw e;
}

Prevention

When it happens

Trigger: PUT a custom image or video generation preset with a name that does not already exist while the kind already stores the maximum number of readable custom presets; hitting the cap after recovering/migrating presets; repeatedly saving presets without deleting.

Common situations: Users accumulating generation presets up to the cap; teams importing preset collections; UI not surfacing the remaining-preset count.

Related errors


AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15). Data as JSON: /api/errors/57cdfcbe452f6746. Report an issue: GitHub.