unslothai/unsloth · error · HTTPException
Refusing to delete storage root
Error message
Refusing to delete storage root
What it means
Refuses when the validated target path equals the storage root itself (target_path == allowed_root at models.py:2957). Deleting the root would wipe every trained and exported model at once, so even a well-formed request naming the root directory is rejected with 400. It usually means model_path was the bare root or resolved to it (e.g. "." inside the root, or a path of only dot-dot segments landing exactly on the root).
Source
Thrown at studio/backend/routes/models.py:2957
raise HTTPException(
status_code = 400,
detail = "Model path is outside Unsloth storage",
)
else:
target_path = delete_path
else:
target_path = target_path.resolve()
should_check_resolved_path = not delete_path_is_symlink or (
export_type == "gguf" and gguf_variant
)
if should_check_resolved_path and not _is_path_under(target_path, allowed_root):
raise HTTPException(
status_code = 400,
detail = "Model path is outside Unsloth storage",
)
if target_path == allowed_root:
raise HTTPException(
status_code = 400,
detail = "Refusing to delete storage root",
)
if not target_path.exists() and not target_path.is_symlink():
raise HTTPException(status_code = 404, detail = "Model not found on disk")
if source == "training":
try:
from core.training import get_training_backend
training_backend = get_training_backend()
if training_backend.is_training_active():
raise HTTPException(
status_code = 409,
detail = "Cannot delete trained models while training is running",
)
# The diffusion (Images) trainer is a second independent run on the same storage root, so
# checking only the LLM backend let a delete rmtree a live run's output directory.View on GitHub (pinned to 203007d190)
Solutions
- Target a specific run or export directory, never the root: e.g. outputs/run-42 or exports/model-gguf.
- Client-side, assert the resolved path is a strict descendant: it must have the root as a parent AND differ from it.
- For bulk cleanup, loop over scanned entries (each is a strict descendant) instead of deleting the root.
- Check you did not append '..' or send '.' as the path.
Example fix
# before
requests.delete('/delete-finetuned', json={'model_path': str(outputs_root()), 'source': 'training'})
# after
for run in outputs_root().iterdir():
if run.is_dir():
requests.delete('/delete-finetuned', json={'model_path': str(run), 'source': 'training'}) Defensive patterns
Strategy: validation
Validate before calling
root = outputs_root().resolve() if source == 'training' else exports_root().resolve() target = Path(model_path).expanduser().resolve() assert target != root and root in target.parents, 'refusing: target is the storage root'
Type guard
def is_specific_model_path(model_path: str, source: str) -> bool:
root = (outputs_root() if source == 'training' else exports_root()).resolve()
t = Path(model_path).expanduser().resolve()
return t != root and root in t.parents Prevention
- Never derive model_path with dirname()/os.path.dirname one level above a known model file.
- Bulk-delete by iterating scanned entries, never by naming the root.
- Strip trailing '/..' or '.' components before sending.
When it happens
Trigger: model_path = "/unsloth-outputs" with source="training"; model_path = "." when the server cwd is the root; model_path = "/exports/my-export/.." which resolves to /exports; UI 'delete all' feature mistakenly passing the root as a single path.
Common situations: Clients that derive model_path as dirname of a real path one level too high; scripts that normalize paths and strip the last component; calling the endpoint with the folder shown at the top of a file picker.
Related errors
- Model path is outside Unsloth storage
- GGUF variant deletion requires an export directory
- model_path is required
- gguf_variant is required when export_type is 'gguf'
- Unload the model before deleting
AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15).
Data as JSON: /api/errors/b6d3800da04db1d4.
Report an issue: GitHub.