unslothai/unsloth · warning · HTTPException

Invalid preset name

Error message

Invalid preset name

What it means

HTTP 422 raised directly by the DELETE /settings/generation-presets/{kind}/custom route (not pydantic) after it strips the query parameter name. It rejects empty names, the reserved name 'Default', and names longer than 80 characters — mirroring the create-side validator. A valid name that simply does not exist deletes nothing and still returns {'deleted': true}, so this 422 is purely about name shape.

Source

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

    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")
def delete_custom_generation_preset(
    kind: Literal["image", "video"],
    name: str,
    current_subject: str = Depends(get_current_subject),
) -> dict[str, bool]:
    name = name.strip()
    if not name or name == "Default" or len(name) > 80:
        raise HTTPException(status_code = 422, detail = "Invalid preset name")
    delete_media_generation_preset(kind, name)
    return {"deleted": True}


class UploadLimitPayload(BaseModel):
    max_upload_size_mb: int = Field(..., ge = MIN_UPLOAD_LIMIT_MB, le = MAX_UPLOAD_LIMIT_MB)


class UploadLimitResponse(BaseModel):
    max_upload_size_mb: int
    max_upload_size_bytes: int
    max_upload_size_label: str
    default_upload_size_mb: int
    min_upload_size_mb: int = MIN_UPLOAD_LIMIT_MB
    max_allowed_upload_size_mb: int = MAX_UPLOAD_LIMIT_MB


class HuggingFaceTokenPayload(BaseModel):

View on GitHub (pinned to 203007d190)

Solutions

  1. Send the exact stored custom preset name, trimmed, 1-80 chars, not 'Default'.
  2. Guard in the client: skip the DELETE when the row is the built-in default preset.
  3. If names mismatch, GET the preset list first and delete by the returned name.

Example fix

// before
await api.delete(`/settings/generation-presets/${kind}/custom?name=${raw}`); // raw = '  '

// after
const name = raw.trim();
if (name && name !== 'Default' && name.length <= 80) {
  await api.delete(`/settings/generation-presets/${kind}/custom?name=${encodeURIComponent(name)}`);
}
Defensive patterns

Strategy: validation

Validate before calling

function validDeleteName(raw: string): boolean {
  const name = raw.trim();
  return name.length >= 1 && name.length <= 80 && name !== 'Default';
}
if (validDeleteName(row.name)) {
  await api.delete(`/settings/generation-presets/${kind}/custom?name=${encodeURIComponent(row.name.trim())}`);
}

Type guard

function isDeletablePresetName(v: unknown): v is string {
  return typeof v === 'string' && v.trim().length >= 1 && v.trim().length <= 80 && v.trim() !== 'Default';
}

Try / catch

try { await api.delete(url); }
catch (e) {
  if (e.status === 422) { skipRow(); return; } // name shape invalid — nothing to delete
  throw e;
}

Prevention

When it happens

Trigger: DELETE with name="" or whitespace-only (URL-encoded %20), name=Default, or a name over 80 characters; often a frontend bug sending an untrimmed or empty string.

Common situations: Delete button clicked on a row whose name field is empty; sending the display label 'Default' instead of a real custom preset name; trailing whitespace introduced by copy-paste or URL encoding of spaces.

Related errors


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