unslothai/unsloth · error · HTTPException
Dataset '{cleaned}' must not be a symbolic link.
Error message
Dataset '{cleaned}' must not be a symbolic link. What it means
HTTP 400 from _resolve_dataset_folder: the dataset directory under the Studio datasets root is itself a symbolic link. The check exists because the sibling containment check (resolve().relative_to(root)) is supplemented by an explicit symlink refusal for the folder — _safe_dataset_image_path only validates each image path, not the dataset directory. This blocks replacing a dataset with a link to arbitrary locations (e.g. /etc, another user's data).
Source
Thrown at studio/backend/routes/training.py:3616
# ── Dataset labeling (per-image caption editing) + one-click example imports ──
# Thumbnails live in a hidden subdir so they never appear in dataset listings or the trainer's image discovery.
_THUMBS_DIRNAME = ".thumbs"
_MAX_CAPTION_CHARS = 2000
def _resolve_dataset_folder(name: str, *, must_exist: bool = True) -> Path:
"""Validate ``name`` (single component, no traversal) and resolve it under the Studio
datasets root. 404 when a read target is missing."""
from utils.paths import datasets_root
cleaned = _clean_diffusion_dataset_name(name)
root = datasets_root().resolve()
folder = root / cleaned
# Reject a symlinked dataset directory and prove the resolved folder stays under root: _safe_dataset_image_path only checks each image path, not the folder.
if folder.is_symlink():
raise HTTPException(
status_code = 400,
detail = f"Dataset '{cleaned}' must not be a symbolic link.",
)
if must_exist and not folder.is_dir():
raise HTTPException(status_code = 404, detail = f"Dataset '{cleaned}' not found.")
try:
folder.resolve(strict = must_exist).relative_to(root)
except (OSError, ValueError):
raise HTTPException(
status_code = 400,
detail = f"Dataset '{cleaned}' escapes the Studio datasets directory.",
)
return folder
# Match diffusion's 4096px decoded-image limit.
_MAX_TRAINING_IMAGE_SIDE = 4096
View on GitHub (pinned to 203007d190)
Solutions
- Replace the symlink with a real directory: remove the link, mkdir the folder, and move/copy the actual image files into it (or upload them through the API).
- If the data lives on another volume, bind-mount it at the datasets root (or move the datasets root setting to that volume) instead of symlinking an individual dataset.
- Recreate the dataset by uploading through the Studio upload endpoint so all files physically live under the root.
Example fix
// shell fix mv /studio/datasets/myset /tmp/myset-link-target # the symlink mkdir /studio/datasets/myset # copy real content in cp -L /tmp/myset-link-target/* /studio/datasets/myset/
Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
from utils.paths import datasets_root
def dataset_is_safe(name: str) -> bool:
folder = datasets_root() / name
return folder.exists() and not folder.is_symlink() and folder.is_dir() Type guard
def is_symlink_dataset_error(exc: HTTPException) -> bool:
return exc.status_code == 400 and 'symbolic link' in exc.detail Try / catch
try:
folder = _resolve_dataset_folder(name)
except HTTPException as e:
if e.status_code == 400 and 'symbolic link' in e.detail:
# tell the user to materialize the dataset; do not retry blindly
raise DatasetLinkError(name) from e
raise Prevention
- Never symlink external data into the datasets root; copy it or relocate the root.
- Audit the datasets root after restores/migrations: find <root> -maxdepth 1 -type l.
- Keep datasets managed exclusively through Studio's upload/import endpoints.
When it happens
Trigger: Any dataset route called with a dataset name whose directory under the datasets root is a symlink: GET/POST image listing, image serving (?thumb=), caption update, image delete, dataset summary. E.g. ln -s /mnt/nas/photos <datasets_root>/myset then GET /training/diffusion/dataset/myset.
Common situations: Users symlinking a NAS mount or another training output into the datasets directory to save disk; restoring a dataset from a backup tool that recreates directories as links; shared/multi-user setups where one user linked another's dataset.
Related errors
- Dataset '{cleaned}' escapes the Studio datasets directory.
- Model path is outside Unsloth storage
- Invalid image filename.
- Invalid block_id: outside upload root
- Path is not in the browseable allowlist. Register it via POS
AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15).
Data as JSON: /api/errors/bc23de8827109439.
Report an issue: GitHub.