unslothai/unsloth · warning · HTTPException

Not a directory: {os.path.basename(str(current))}

Error message

Not a directory: {os.path.basename(str(current))}

What it means

Raised as a 400 by _resolve_browse_target when the fully resolved target exists, passed the allowlist/sensitive/system checks, but current.is_dir() is False — the path points at a regular file (or a non-directory entry). The browse endpoint lists directory contents, so a file target is a client error, not a server fault.

Source

Thrown at studio/backend/routes/models.py:1732

                raise HTTPException(
                    status_code = 403,
                    detail = "System directories are not browseable.",
                )
            current = resolved_child

        if contains_sensitive_path_component(str(current)):
            raise HTTPException(
                status_code = 403,
                detail = "Credential or configuration directories are not browseable.",
            )
        # Zero-component case: the requested path IS an allowlist root (legacy "/" or a drive root).
        if is_denied_system_path(str(current)):
            raise HTTPException(
                status_code = 403,
                detail = "System directories are not browseable.",
            )
        if not current.is_dir():
            raise HTTPException(
                status_code = 400,
                detail = f"Not a directory: {os.path.basename(str(current))}",
            )
        return current

    raise HTTPException(
        status_code = 403,
        detail = (
            "Path is not in the browseable allowlist. Register it via "
            "POST /api/models/scan-folders first, or pick a directory "
            "under your home folder."
        ),
    )


# Sync (def, not async) so FastAPI runs the blocking filesystem I/O in the threadpool: a
# disconnected mapped drive can make the probe wait out its timeout, which on the event
# loop would stall every other request. Matches the hub browse endpoint.

View on GitHub (pinned to 203007d190)

Solutions

  1. Pass the containing directory instead of the file: strip the last path segment before calling browse-folders.
  2. In the UI, only make directories navigable — disable browse for file entries.
  3. If you intended to select a model file, use the local-models listing endpoint rather than the folder browser.

Example fix

// before
fetch(`/api/models/browse-folders?path=${selectedEntry.path}`); // selectedEntry is a file -> 400

// after
if (selectedEntry.isDir) {
  fetch(`/api/models/browse-folders?path=${selectedEntry.parentPath ?? selectedEntry.path}`);
}
Defensive patterns

Strategy: type-guard

Validate before calling

import os

if not os.path.isdir(os.path.realpath(browse_path)):
    browse_path = os.path.dirname(browse_path.rstrip('/')) or home_dir()
# now safe to call browse-folders

Type guard

function isBrowsableDir(entry: {path: string; isDir?: boolean}): boolean {
  return entry.isDir === true && !entry.path.endsWith('.safetensors');
}

Try / catch

try {
  entries = await browse(path);
} catch (e) {
  if (e.status === 400 && /Not a directory/.test(e.detail)) {
    entries = await browse(parentOf(path));
  } else throw e;
}

Prevention

When it happens

Trigger: GET browse-folders?path=/home/user/models/model.safetensors — walking succeeds to the file, every security check passes, and the final is_dir() guard rejects it.

Common situations: Folder picker fed a file path (drag-drop of a model file, remembered selection, path input autocomplete); front-end confusing a file entry with a directory in its tree model.

Related errors


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