unslothai/unsloth · error · ValueError

dataset path must be relative or under a dataset root: {raw!

Error message

dataset path must be relative or under a dataset root: {raw!r}

What it means

Raised by resolve_dataset_path() when the input is an absolute path that is not contained under any of the three allowed dataset roots (datasets_root(), dataset_uploads_root(), recipe_datasets_root()). The function only accepts relative paths (optionally prefixed with 'uploads/', 'recipes/', or 'assets/datasets/') or absolute paths already inside a managed root; anything else absolute is rejected to prevent reading arbitrary filesystem locations.

Source

Thrown at studio/backend/utils/paths/storage_roots.py:510

        strip_prefixes = ("runs", "tensorboard"),
    )


def resolve_dataset_path(path_value: str) -> Path:
    raw = str(path_value or "").strip()
    if "\x00" in raw:
        raise ValueError("dataset path may not contain null bytes")
    path = Path(raw).expanduser()
    if ".." in path.parts:
        raise ValueError(f"dataset path may not contain '..' segments: {raw!r}")
    if path.is_absolute():
        for root_fn in (datasets_root, dataset_uploads_root, recipe_datasets_root):
            try:
                _assert_contained(path, root_fn())
                return path
            except ValueError:
                continue
        raise ValueError(f"dataset path must be relative or under a dataset root: {raw!r}")

    parts = [part for part in Path(path_value).parts if part not in ("", ".")]
    if parts[:2] == ["assets", "datasets"]:
        parts = parts[2:]
    if parts and parts[0] == "uploads":
        cleaned = Path(*parts[1:]) if len(parts) > 1 else Path()
        return dataset_uploads_root() / cleaned
    if parts and parts[0] == "recipes":
        cleaned = Path(*parts[1:]) if len(parts) > 1 else Path()
        return recipe_datasets_root() / cleaned

    cleaned = Path(*parts) if parts else Path()
    candidates = [
        dataset_uploads_root() / cleaned,
        recipe_datasets_root() / cleaned,
        datasets_root() / cleaned,
        dataset_uploads_root() / cleaned.name,
        recipe_datasets_root() / cleaned.name,

View on GitHub (pinned to 203007d190)

Solutions

  1. Send the path relative to the dataset root (or with the 'uploads/'/'recipes/' prefix) instead of absolute.
  2. If the path should be valid, verify the configured dataset roots actually contain it: check datasets_root()/dataset_uploads_root()/recipe_datasets_root() return values and move/symlink the data or update configuration.
  3. For data living outside the roots, copy or import it into a managed dataset root first, then reference it relatively.

Example fix

# before
resolve_dataset_path('/srv/old-storage/datasets/cats')  # roots moved to /srv/new-storage

# after
resolve_dataset_path('cats')  # relative to datasets_root()
# or copy data under the new root first:
#   shutil.copytree('/srv/old-storage/datasets/cats', datasets_root() / 'cats')
Defensive patterns

Strategy: validation

Validate before calling

from utils.paths.storage_roots import datasets_root, dataset_uploads_root, recipe_datasets_root

def is_under_dataset_root(p) -> bool:
    try:
        p.relative_to(datasets_root()); return True
    except ValueError: pass
    for root in (dataset_uploads_root(), recipe_datasets_root()):
        try:
            p.relative_to(root); return True
        except ValueError: pass
    return False

if Path(raw).is_absolute() and not is_under_dataset_root(Path(raw).expanduser()):
    raise HTTPBadRequest('send dataset paths relative to the dataset root')

Type guard

def is_resolvable_dataset_path(raw: str) -> bool:
    p = Path(str(raw or '').strip()).expanduser()
    if '\x00' in raw or '..' in p.parts:
        return False
    return not p.is_absolute() or is_under_dataset_root(p)

Try / catch

try:
    path = resolve_dataset_path(raw)
except ValueError as e:
    if 'must be relative or under a dataset root' in str(e):
        return bad_request('dataset path outside managed storage')
    raise

Prevention

When it happens

Trigger: Calling resolve_dataset_path('/home/user/my-data.csv') where that path is outside all configured dataset roots; also '/var/data/uploads/x' when dataset_uploads_root() points elsewhere (e.g. changed storage configuration), or an absolute path on a different drive/prefix spelling than the root (symlinked mount, trailing differences).

Common situations: Storage root configuration changed (moved datasets directory, different machine) while persisted absolute paths in the DB still point at the old location. Users pasting local absolute paths into a form. Case-sensitivity or symlink mismatches on macOS/Windows making a logically-inside path fail the containment assert.

Related errors


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