BerriAI/litellm · error · Exception

Failed to fetch file

Error message

Failed to fetch file '{file_path}' via JSON endpoint: {e}

What it means

Catch-all of the JSON fallback fetch: any non-401/403/404 failure while GETting /repository/files/<path>?ref=... is wrapped here with the original exception text appended. It is reached only after the raw endpoint returned 404, so infrastructure problems stack on top of whatever made the raw request miss.

Solutions

  1. Verify base_url ends with /api/v4 and the host is reachable
  2. Reproduce with curl against the JSON endpoint (the URL printed in the error)
  3. Retry transient 5xx/429/network errors with backoff
  4. Cache prompt files locally to ride out GitLab blips

Example fix

# before
content = client.get_file_content("prompts/greet.prompt")

# after: tolerate transient GitLab failures
for attempt in range(3):
    try:
        content = client.get_file_content("prompts/greet.prompt")
        break
    except Exception as e:
        if attempt == 2 or "via JSON endpoint" not in str(e):
            raise
        time.sleep(2 ** attempt)
Defensive patterns

Strategy: retry

Validate before calling

import socket
from urllib.parse import urlparse


def gitlab_host_reachable(base_url: str) -> bool:
    host = urlparse(base_url).hostname or ""
    try:
        socket.getaddrinfo(host, 443)
        return True
    except socket.gaierror:
        return False

Try / catch

import time


def fetch_with_retry(client, path, attempts=3):
    for i in range(attempts):
        try:
            return client.get_file_content(path)
        except Exception as e:
            msg = str(e)
            retryable = "via JSON endpoint" in msg and any(
                t in msg for t in ("timed out", "ConnectError", "500", "502", "503", "429")
            )
            if i == attempts - 1 or not retryable:
                raise
            time.sleep(2 ** i)

Prevention

When it happens

Trigger: Self-hosted GitLab outage or restart; base_url missing /api/v4; TLS/proxy failures; 5xx or 429 responses; DNS resolution failure; timeouts fetching large prompt files through the JSON API.

Common situations: On-prem GitLab maintenance windows; misrouted private networks; rate-limited automation loops; slow mirrors adding latency to the base64 JSON path.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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

Appendix: source

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

            content: Final = data.get("content")
            encoding: Final = data.get("encoding", "")
            if content and encoding == "base64":
                try:
                    return base64.b64decode(content).decode("utf-8")
                except Exception:
                    return base64.b64decode(content).decode("utf-8", errors="replace")
            return content
        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}' via JSON endpoint: {e}")

    def list_files(
        self,
        directory_path: str = "",
        file_extension: str = ".prompt",
        recursive: bool = False,
        *,
        ref: str | None = None,
    ) -> list[str]:
        """
        List files in a directory with a specific extension using the repository tree API.

        Args:
            directory_path: Directory path in the repository (empty for repo root)
            file_extension: File extension to filter by (default: .prompt)
            recursive: If True, traverses subdirectories
            ref: Optional override (tag/branch/SHA). Defaults to self.ref.

View on GitHub (pinned to 77b7c6c40c)