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
- Pass the containing directory instead of the file: strip the last path segment before calling browse-folders.
- In the UI, only make directories navigable — disable browse for file entries.
- 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
- Only make directories navigable in the picker UI.
- Strip the last segment when a file entry is selected for browsing.
- Use the local-models endpoint, not browse-folders, to target individual model files.
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
- Execution artifact path is not a dataset folder.
- Invalid base64 image data: {exc}
- Image is too large ({w}x{h}); maximum is {max_side}px per si
- Local base_repo is not a diffusers pipeline directory (no {i
- Path does not exist
AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15).
Data as JSON: /api/errors/9f84ecf4a135383b.
Report an issue: GitHub.