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
- Open the repository in the GitLab UI as the token's user to confirm access and the exact project path
- Use a token with read_repository scope and at least Reporter role on the project
- If the branch/tag is protected, fetch from an unprotected ref or grant the token access to it
- 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
- Run a permission probe at startup on one known file
- Document the minimum role/scope (read_repository, Reporter+) required by the integration
- Prefer project access tokens scoped to exactly one repo
- Re-check access after GitLab group/role reorganizations
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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Access denied to directory
- access_denied
- Access denied to directory
- Access denied to file
- Access denied to prompt version
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)