unslothai/unsloth · error · HTTPException

Models folder path is not a directory: {path}

Error message

Models folder path is not a directory: {path}

What it means

HTTP 500 raised when mkdir(parents=True, exist_ok=True) returned without error but path.is_dir() is still False immediately after. This catches the races and edge cases mkdir can mask: path is (or became) a symlink to a regular file, or another process replaced the directory between creation and the check. It guarantees 'Open folder' never receives a non-directory.

Source

Thrown at studio/backend/hub/services/models/local_inventory.py:1003

def get_models_folder_response() -> dict:
    """Return the directory where downloaded models are stored.

    This is the active HF hub cache (honors ``HF_HOME`` / ``HF_HUB_CACHE``);
    the desktop app reveals it in the OS file manager.
    """
    path = _resolve_hf_cache_dir()
    # Create it if missing so "Open folder" works before the first download:
    # HF builds the cache lazily, and studio only pre-creates the *default*
    # dir, not a user's explicit HF_HOME / HF_HUB_CACHE.
    try:
        path.mkdir(parents = True, exist_ok = True)
    except OSError as e:
        raise HTTPException(
            status_code = 500,
            detail = f"Failed to create models folder: {path}: {e}",
        ) from e
    if not path.is_dir():
        raise HTTPException(
            status_code = 500,
            detail = f"Models folder path is not a directory: {path}",
        )
    return {"path": str(path)}


def get_scan_folders_response() -> dict:
    return {"folders": list_scan_folders()}


def add_scan_folder_response(path: str) -> dict:
    try:
        folder, inserted = add_scan_folder_with_status(_coerce_scan_folder_path(path))
    except ValueError as e:
        logger.warning("Scan folder rejected: %s (path=%s)", e, path)
        raise HTTPException(status_code = 400, detail = str(e))
    logger.info("Scan folder added: %s", folder.get("path"))
    if inserted:

View on GitHub (pinned to 203007d190)

Solutions

  1. Inspect the path: `ls -la <HF_HOME>` and confirm it is a directory or a symlink to a directory.
  2. Replace the offending symlink/file with a real directory (rm the link, mkdir -p).
  3. Repoint HF_HOME/HF_HUB_CACHE at a real directory.

Example fix

# before: HF_HOME=$HOME/.cache/hf  where ~/.cache/hf -> somefile
rm ~/.cache/hf && mkdir -p ~/.cache/hf
# after: HF_HOME=$HOME/.cache/hf  (real directory)
Defensive patterns

Strategy: validation

Validate before calling

import os

def cache_path_is_real_dir() -> bool:
    p = os.environ.get("HF_HOME") or os.path.expanduser("~/.cache/huggingface")
    return os.path.isdir(p) and not os.path.islink(os.path.realpath(p) and p or p) or os.path.isdir(os.path.realpath(p))
# simpler:
def cache_ok() -> bool:
    p = os.environ.get("HF_HOME") or os.path.expanduser("~/.cache/huggingface")
    return os.path.isdir(p) and os.path.isdir(os.path.realpath(p))

Prevention

When it happens

Trigger: HF_HOME pointing at a symlink that resolves to a regular file; a concurrent process swapping the directory for a file (or a symlink to one) in the microseconds between mkdir and is_dir; exist_ok=True silently accepting an existing file at that name is NOT possible (mkdir would raise), so the realistic case is symlink-to-file.

Common situations: Users symlinking HF_HOME to a 'cache' that is actually a file; symlink chains created by dotfile managers; a broken symlink target that happens to be a file.

Related errors


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