unslothai/unsloth · error · HTTPException

Only trained or exported Unsloth models can be deleted

Error message

Only trained or exported Unsloth models can be deleted

What it means

Raised as a 400 by the Unsloth model-delete endpoint when the request body's source field is not one of {'training', 'exported'}. The endpoint deliberately deletes only artifacts Unsloth itself created under its outputs/exports roots, so any other source value is refused before path validation begins.

Source

Thrown at studio/backend/routes/models.py:2900

        deleted_count += 1
    return deleted_count, deleted_bytes


@router.delete("/delete-finetuned")
async def delete_finetuned_model(
    model_path: str = Body(...),
    source: str = Body(...),
    export_type: Optional[str] = Body(None),
    gguf_variant: Optional[str] = Body(None),
    current_subject: str = Depends(get_current_subject),
):
    """Delete an Unsloth-trained or exported model from disk.

    Only paths under Unsloth's outputs/exports roots are accepted.
    Exported GGUF entries can delete one quant variant at a time.
    """
    if source not in {"training", "exported"}:
        raise HTTPException(
            status_code = 400,
            detail = "Only trained or exported Unsloth models can be deleted",
        )

    if not model_path or not model_path.strip():
        raise HTTPException(status_code = 400, detail = "model_path is required")

    if export_type == "gguf" and not gguf_variant:
        raise HTTPException(
            status_code = 400,
            detail = "gguf_variant is required when export_type is 'gguf'",
        )

    raw_path = Path(model_path).expanduser()
    if source == "training":
        target_path = raw_path
        allowed_root = outputs_root()
    else:

View on GitHub (pinned to 203007d190)

Solutions

  1. Send source='training' for Unsloth-trained checkpoints or source='exported' for exported artifacts (including per-variant GGUF deletes with export_type='gguf' and gguf_variant).
  2. Do not use this endpoint for Hub-downloaded or arbitrary local models — remove those with your own file tooling.
  3. Check casing: the comparison is exact against the lowercase literals.

Example fix

# before
client.post('/api/models/unsloth/delete', json={'model_path': p, 'source': 'Exported'})  # 400

# after
client.post('/api/models/unsloth/delete', json={'model_path': p, 'source': 'exported', 'export_type': 'gguf', 'gguf_variant': 'Q4_K_M'})
Defensive patterns

Strategy: type-guard

Validate before calling

const DELETABLE_SOURCES = ['training', 'exported'] as const;
if (!DELETABLE_SOURCES.includes(entry.source)) {
  throw new Error(`cannot delete ${entry.source} models here — only Unsloth 'training'/'exported' artifacts`); 
}

Type guard

type UnslothSource = 'training' | 'exported';
function isDeletableSource(s: unknown): s is UnslothSource {
  return s === 'training' || s === 'exported';
}

Try / catch

try {
  await deleteModel({model_path, source});
} catch (e) {
  if (e.status === 400 && /Only trained or exported/.test(e.detail)) {
    disableDeleteButton(); // entry is not an Unsloth artifact
  } else throw e;
}

Prevention

When it happens

Trigger: POST to the delete endpoint with body {"model_path": ..., "source": "hub"} (or 'custom', 'local', missing) — anything outside the two allowed literals.

Common situations: Front-end delete button wired to a generic model list that includes Hub/local entries; API consumers assuming the endpoint is a general model deleter; typos/casing ('Exported' vs 'exported').

Related errors


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