{"record":{"id":"1d3ff91e710a3a1d","repo":"BerriAI/litellm","slug":"failed-to-fetch-file-file-path-via-json-endpoi","errorCode":null,"errorMessage":"Failed to fetch file '{file_path}' via JSON endpoint: {e}","messagePattern":"Failed to fetch file '(.+?)' via JSON endpoint: (.+?)","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"litellm/integrations/gitlab/gitlab_client.py","lineNumber":181,"sourceCode":"            content: Final = data.get(\"content\")\n            encoding: Final = data.get(\"encoding\", \"\")\n            if content and encoding == \"base64\":\n                try:\n                    return base64.b64decode(content).decode(\"utf-8\")\n                except Exception:\n                    return base64.b64decode(content).decode(\"utf-8\", errors=\"replace\")\n            return content\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}' via JSON endpoint: {e}\")\n\n    def list_files(\n        self,\n        directory_path: str = \"\",\n        file_extension: str = \".prompt\",\n        recursive: bool = False,\n        *,\n        ref: str | None = None,\n    ) -> list[str]:\n        \"\"\"\n        List files in a directory with a specific extension using the repository tree API.\n\n        Args:\n            directory_path: Directory path in the repository (empty for repo root)\n            file_extension: File extension to filter by (default: .prompt)\n            recursive: If True, traverses subdirectories\n            ref: Optional override (tag/branch/SHA). Defaults to self.ref.\n","sourceCodeStart":163,"sourceCodeEnd":199,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/integrations/gitlab/gitlab_client.py#L163-L199","documentation":"Raised by GitLabClient.get_file_content when fetching a file via the GitLab repository files JSON endpoint fails with an error other than 404 (returns None), 403, or 401. The original exception (typically httpx.HTTPStatusError or a network error) is stringified into the message. This is the catch-all for 5xx responses, timeouts, DNS failures, and malformed base_url values.","triggerScenarios":"Calling get_file_content() (directly or via GitLabPromptManager prompt loading) when GitLab returns 500/502/503, when base_url is unreachable or misconfigured (wrong host, proxy interference), when the project path is URL-encoded incorrectly, or when httpx raises a connection/timeout error that has no .response attribute.","commonSituations":"Self-hosted GitLab instance down or restarting; corporate proxy blocking the request; base_url pointing to an API path that doesn't exist; rate limiting returning 429; typo in project slug producing unexpected error pages instead of clean 404s.","solutions":["Inspect the wrapped exception text {e} to identify the real HTTP status or network error","If it is a 5xx or timeout, retry after a delay or check the GitLab instance health","Verify base_url (should be like https://gitlab.com/api/v4) and that the project identifier 'group/project' is correct","Check for proxies/SSL interception between the host and GitLab","If 429 rate limiting, reduce load_all_prompts() frequency or add caching"],"exampleFix":"// before\ncontent = client.get_file_content('prompts/foo.prompt')\n\n// after - surface status and retry transient failures\nimport time\nfor attempt in range(3):\n    try:\n        content = client.get_file_content('prompts/foo.prompt')\n        break\n    except Exception as e:\n        if attempt == 2 or '429' not in str(e) and '5' not in str(e)[:50]:\n            raise\n        time.sleep(2 ** attempt)","handlingStrategy":"retry","validationCode":"import httpx\n\ndef gitlab_reachable(base_url: str) -> bool:\n    try:\n        r = httpx.get(f\"{base_url}/version\", timeout=5)\n        return r.status_code == 200\n    except httpx.HTTPError:\n        return False","typeGuard":null,"tryCatchPattern":"try:\n    content = client.get_file_content(path, ref=ref)\nexcept Exception as e:\n    msg = str(e)\n    if any(code in msg for code in (\"429\", \"500\", \"502\", \"503\", \"timed out\")):\n        # transient: retry with backoff\n        raise RetryableError(msg) from e\n    raise","preventionTips":["Run a connectivity check to {base_url}/version at startup","Wrap bulk loads in retry with exponential backoff for 5xx/429","Cache fetched prompt files to reduce API exposure","Log the full wrapped exception, not just the message"],"tags":["gitlab","http","network","api-client","prompt-management"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}