unslothai/unsloth · error · HTTPException

Permission denied reading {os.path.basename(str(target))}

Error message

Permission denied reading {os.path.basename(str(target))}

What it means

Raised as a 403 by the browse-folders listing handler when target.iterdir() throws PermissionError on the final, already-validated target directory (unlike errors 1104/1105, which occur during the walk to the target). The backend can see the directory exists and is allowlisted, but the process lacks read permission to enumerate its entries.

Source

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

    try:
        target = _resolve_browse_target(path, allowed_roots)
    except HTTPException:
        requested_path = _normalize_browse_request_path(path)
        if path is not None and path.strip():
            logger.warning(
                "browse-folders: rejected path %r (normalized=%s)",
                path,
                requested_path,
            )
        raise

    entries: list[BrowseEntry] = []
    truncated = False
    visited = 0
    try:
        it = target.iterdir()
    except PermissionError:
        raise HTTPException(
            status_code = 403,
            detail = f"Permission denied reading {os.path.basename(str(target))}",
        )
    except OSError as exc:
        logger.warning("browse-folders: could not read %s: %s", target, exc, exc_info = True)
        raise HTTPException(
            status_code = 500,
            detail = f"Could not read {os.path.basename(str(target))}",
        )

    try:
        for child in it:
            # Bound by *visited*, not *appended*: a cap on len(entries) would never trigger in dirs
            # full of files. Counting visits caps worst-case work at ``_BROWSE_ENTRY_CAP``.
            visited += 1
            if visited > _BROWSE_ENTRY_CAP:
                truncated = True
                break

View on GitHub (pinned to 203007d190)

Solutions

  1. Grant read+execute on the directory to the backend user: chmod a+rx /path/to/dir or set the ACL accordingly.
  2. chown the directory to the user running the studio backend.
  3. If intentional restriction, browse a different, readable directory.

Example fix

# backend runs as 'beagle', dir owned by root mode 700
# before
GET /api/models/browse-folders?path=/data/models  # 403

# after (root shell)
# chown -R beagle:beagle /data/models
GET /api/models/browse-folders?path=/data/models
Defensive patterns

Strategy: validation

Validate before calling

import os

def listable(d: str) -> bool:
    try:
        it = os.scandir(d); next(iter(it), None); return True
    except PermissionError:
        return False
    except OSError:
        return False

if not listable(target_dir):
    raise PermissionError(f'backend lacks read access to {target_dir}')

Try / catch

try:
    entries = browse(dir)
except HTTPError as e:
    if e.response.status_code == 403 and 'Permission denied' in e.response.json()['detail']:
        show_user('The backend process cannot read this folder — fix ownership/mode and retry.')
    else: raise

Prevention

When it happens

Trigger: GET browse-folders?path=<allowed-dir> where the directory mode/ACL denies read to the backend user; directory owned by root with 700 while studio runs unprivileged.

Common situations: Models directory created by root or a container and not chowned; chmod 600 applied to a directory by mistake; Windows ACLs from a copied folder denying list access.

Related errors


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