BerriAI/litellm · error · Exception

Access denied to directory

Error message

Access denied to directory '{directory_path}'. Check your GitLab permissions for project '{self.project}'.

What it means

GitLab answered 403 Forbidden for the repository tree API used by list_files (GET /projects/<id>/repository/tree?ref=...). The token authenticated but cannot read this project/directory: role below Reporter on a private project, missing read_repository scope, or a path under a repo the identity cannot see. Note that a 404 returns an empty list instead of raising, so this error specifically means the identity is being denied, not that the path is missing.

Solutions

  1. Confirm the token's user can browse that directory in the GitLab UI
  2. Use read_repository scope and Reporter+ role on the project
  3. Check the directory path spelling/case and that it lives in the configured project
  4. Verify you pointed at the right project (a wrong project that exists and is private yields 403)

Example fix

# before: token without repository read on a private project
files = client.list_files("prompts/internal")

# after: token with read_repository scope and a path the identity can see
files = client.list_files("prompts")
Defensive patterns

Strategy: try-catch

Validate before calling

# Fail fast at startup instead of discovering 403s during prompt listing
if not client.test_connection():
    raise RuntimeError("GitLab credentials/project invalid")
client.list_files("prompts")  # exercises the tree API at boot

Try / catch

try:
    files = client.list_files("prompts")
except Exception as e:
    if "Access denied to directory" in str(e):
        raise RuntimeError(
            "Token cannot list the prompts directory - "
            "grant read_repository / Reporter+ or fix the path"
        ) from e
    raise

Prevention

When it happens

Trigger: list_files('prompts') with a Guest-role token; PAT without read_repository scope; directory path belonging to a different (private) subgroup repo; protected ref with an unprivileged token.

Common situations: Startup prompt discovery running with a least-privilege CI token; org restructures moving repositories between groups; scope changes after token rotation.

Understand the failure class

Related errors


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/a354fdb04254facf. Report an issue: GitHub.

Appendix: source

Thrown at litellm/integrations/gitlab/gitlab_client.py:225

            if resp.status_code == 404:
                return []
            resp.raise_for_status()

            data: Final = resp.json() or []
            files: Final[list[str]] = []
            for item in data:
                if item.get("type") == "blob":
                    file_path = item.get("path", "")
                    if not file_extension or file_path.endswith(file_extension):
                        files.append(file_path)
            return files

        except Exception as e:
            status: Final = getattr(getattr(e, "response", None), "status_code", None)
            if status == 404:
                return []
            if status == 403:
                raise Exception(
                    f"Access denied to directory '{directory_path}'. Check your GitLab permissions for project '{self.project}'."
                )
            if status == 401:
                raise Exception("Authentication failed. Check your GitLab token and auth_method.")
            raise Exception(f"Failed to list files in '{directory_path}': {e}")

    def get_repository_info(self) -> dict[str, Any]:
        """Get information about the project/repository."""
        url: Final = f"{self.base_url}/projects/{self._project_enc}"
        try:
            resp: Final = self.http_handler.get(url, headers=self.headers)
            resp.raise_for_status()
            return resp.json()
        except Exception as e:
            raise Exception(f"Failed to get repository info: {e}")

    def test_connection(self) -> bool:
        """Test the connection to the GitLab project."""

View on GitHub (pinned to 77b7c6c40c)