BerriAI/litellm · error · Exception

Access denied to file

Error message

Access denied to file '{file_path}'. Check your GitLab permissions for project '{self.project}'.

What it means

GitLab answered 403 Forbidden for the raw file endpoint (/repository/files/<path>/raw). The status is recovered from the httpx exception attached by raise_for_status(). 403 means the token authenticated but the identity lacks permission for this repo/file: role below Reporter on a private project, missing read_repository scope, a protected branch/tag, or a wrong project path that GitLab hides with 403 instead of 404.

Solutions

  1. Open the repository in the GitLab UI as the token's user to confirm access and the exact project path
  2. Use a token with read_repository scope and at least Reporter role on the project
  3. If the branch/tag is protected, fetch from an unprotected ref or grant the token access to it
  4. Verify project is exactly 'group/subgroup/repo' (or use the numeric project id)

Example fix

# before: read_api-only PAT on a private repo -> 403 on raw fetch
gitlab_config = {"project": "mygroup/myrepo", "access_token": read_api_token}

# after: PAT with read_repository scope and Reporter+ role
gitlab_config = {"project": "mygroup/myrepo", "access_token": read_repo_token}
Defensive patterns

Strategy: try-catch

Validate before calling

# Startup probe: fail fast on permission problems instead of at request time
if not client.test_connection():
    raise RuntimeError("GitLab token/project invalid - check credentials")
probe = client.get_file_content("prompts/.access_probe")  # known small file
# 404 (None) is fine; a 403 raises here at boot

Try / catch

try:
    content = client.get_file_content("prompts/greet.prompt")
except Exception as e:
    if "Access denied to file" in str(e):
        raise RuntimeError(
            "Token lacks repository read access - "
            "grant read_repository / Reporter+ or fix the project path"
        ) from e
    raise

Prevention

When it happens

Trigger: get_file_content(...) with a PAT whose role is below Reporter on a private project; token without read_repository scope; fetching from a protected branch or tag with an unprivileged token; project spelled 'group/repo' pointing at a repository the token's user cannot see.

Common situations: Group-owned projects with per-subgroup access rules; fine-grained PAT scopes; CI job tokens used outside their pipeline; role changes after team rotation or offboarding.

Understand the failure class

Related errors


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

Appendix: source

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

            if resp.status_code == 404:
                # Fallback to JSON endpoint
                return self._get_file_content_via_json(file_path, ref=ref)
            resp.raise_for_status()

            ctype: Final = (resp.headers.get("content-type") or "").lower()
            if ctype.startswith("text/") or "charset=" in ctype or ctype.startswith("application/json"):
                return resp.text
            try:
                return resp.content.decode("utf-8")
            except Exception:
                return resp.content.decode("utf-8", errors="replace")

        except Exception as e:
            status: Final = getattr(getattr(e, "response", None), "status_code", None)
            if status == 404:
                return None
            if status == 403:
                raise Exception(
                    f"Access denied to file '{file_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 fetch file '{file_path}': {e}")

    def _get_file_content_via_json(self, file_path: str, *, ref: str | None = None) -> str | None:
        """
        Fallback for get_file_content(): use the JSON file API which returns base64 content.
        """
        json_url: Final = self._file_json_url(file_path, ref=ref)
        try:
            resp: Final = self.http_handler.get(json_url, headers=self.headers)
            if resp.status_code == 404:
                return None
            resp.raise_for_status()
            data: Final = resp.json()
            content: Final = data.get("content")

View on GitHub (pinned to 77b7c6c40c)