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

  1. 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).
  2. 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.
  3. 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

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


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