{"record":{"id":"612ff51723deee1e","repo":"BerriAI/litellm","slug":"access-denied-to-file-file-path-check-your-gi","errorCode":null,"errorMessage":"Access denied to file '{file_path}'. Check your GitLab permissions for project '{self.project}'.","messagePattern":"Access denied to file '(.+?)'\\. Check your GitLab permissions for project '(.+?)'\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"litellm/integrations/gitlab/gitlab_client.py","lineNumber":145,"sourceCode":"            if resp.status_code == 404:\n                # Fallback to JSON endpoint\n                return self._get_file_content_via_json(file_path, ref=ref)\n            resp.raise_for_status()\n\n            ctype: Final = (resp.headers.get(\"content-type\") or \"\").lower()\n            if ctype.startswith(\"text/\") or \"charset=\" in ctype or ctype.startswith(\"application/json\"):\n                return resp.text\n            try:\n                return resp.content.decode(\"utf-8\")\n            except Exception:\n                return resp.content.decode(\"utf-8\", errors=\"replace\")\n\n        except Exception as e:\n            status: Final = getattr(getattr(e, \"response\", None), \"status_code\", None)\n            if status == 404:\n                return None\n            if status == 403:\n                raise Exception(\n                    f\"Access denied to file '{file_path}'. Check your GitLab permissions for project '{self.project}'.\"\n                )\n            if status == 401:\n                raise Exception(\"Authentication failed. Check your GitLab token and auth_method.\")\n            raise Exception(f\"Failed to fetch file '{file_path}': {e}\")\n\n    def _get_file_content_via_json(self, file_path: str, *, ref: str | None = None) -> str | None:\n        \"\"\"\n        Fallback for get_file_content(): use the JSON file API which returns base64 content.\n        \"\"\"\n        json_url: Final = self._file_json_url(file_path, ref=ref)\n        try:\n            resp: Final = self.http_handler.get(json_url, headers=self.headers)\n            if resp.status_code == 404:\n                return None\n            resp.raise_for_status()\n            data: Final = resp.json()\n            content: Final = data.get(\"content\")","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/integrations/gitlab/gitlab_client.py#L127-L163","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)"],"exampleFix":"# before: read_api-only PAT on a private repo -> 403 on raw fetch\ngitlab_config = {\"project\": \"mygroup/myrepo\", \"access_token\": read_api_token}\n\n# after: PAT with read_repository scope and Reporter+ role\ngitlab_config = {\"project\": \"mygroup/myrepo\", \"access_token\": read_repo_token}","handlingStrategy":"try-catch","validationCode":"# Startup probe: fail fast on permission problems instead of at request time\nif not client.test_connection():\n    raise RuntimeError(\"GitLab token/project invalid - check credentials\")\nprobe = client.get_file_content(\"prompts/.access_probe\")  # known small file\n# 404 (None) is fine; a 403 raises here at boot","typeGuard":null,"tryCatchPattern":"try:\n    content = client.get_file_content(\"prompts/greet.prompt\")\nexcept Exception as e:\n    if \"Access denied to file\" in str(e):\n        raise RuntimeError(\n            \"Token lacks repository read access - \"\n            \"grant read_repository / Reporter+ or fix the project path\"\n        ) from e\n    raise","preventionTips":["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"],"tags":["gitlab","permissions","http-403","forbidden"],"backgroundTag":"http-403-forbidden","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}