BerriAI/litellm · error · Exception

Authentication failed. Check your BitBucket access token…

Error message

Authentication failed. Check your BitBucket access token and permissions.

What it means

get_file_content received HTTP 401 from the BitBucket src endpoint: the credential itself was rejected — expired or revoked app password/access token, a malformed Bearer header, or an auth-mode mismatch where app-password credentials are sent as a Bearer token (the client defaults to auth_method="token"; basic auth with username is only used when configured).

Solutions

  1. Regenerate the app password / access token and update the config or vault it came from
  2. For app passwords set auth_method="basic" and username so the client sends Basic auth instead of Bearer
  3. Trim whitespace/newlines when loading the secret (e.g., .strip() on env reads)
  4. Restart the process after credential rotation so the cached client headers are rebuilt

Example fix

// before
BitBucketClient({"workspace": "acme", "repository": "r",
                 "access_token": app_password})  # default Bearer -> 401
// after
BitBucketClient({"workspace": "acme", "repository": "r",
                 "access_token": app_password,
                 "auth_method": "basic", "username": "svc-prompts"})
Defensive patterns

Strategy: try-catch

Validate before calling

import base64, os

token = os.environ.get("BITBUCKET_APP_PASSWORD", "").strip()
if not token:
    raise RuntimeError("BITBUCKET_APP_PASSWORD is empty or unset")

Type guard

def looks_like_credential(secret: str) -> bool:
    s = (secret or "").strip()
    return len(s) >= 20 and not any(c.isspace() for c in s)

Try / catch

try:
    content = client.get_file_content(path)
except Exception as e:
    if "Authentication failed" in str(e):
        logger.error("bitbucket 401: credential expired/revoked or wrong auth_method")
        raise
    raise

Prevention

When it happens

Trigger: App password revoked when the owning account changed/rotated it; sending an app password with the default Bearer scheme; token pasted with whitespace/newlines from a secret file; workspace migrated to API tokens while the old credential type was deprecated.

Common situations: Silent expiry of bitbucket-tokens during long-running services; secrets rotated in vault but not reloaded by the process; copying a token for a different Atlassian product (e.g., a Jira API token).

Understand the failure class

Related errors


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

Appendix: source

Thrown at litellm/integrations/bitbucket/bitbucket_client.py:114

                return response.text
            else:
                # For binary files or when content-type is not text, try to decode as base64
                try:
                    return base64.b64decode(response.content).decode("utf-8")
                except Exception:
                    return response.text

        except Exception as e:
            # Check if it's an HTTP error
            if hasattr(e, "response") and hasattr(e.response, "status_code"):
                if e.response.status_code == 404:
                    return None
                elif e.response.status_code == 403:
                    raise Exception(
                        f"Access denied to file '{file_path}'. Check your BitBucket permissions for workspace '{self.workspace}' and repository '{self.repository}'."
                    )
                elif e.response.status_code == 401:
                    raise Exception("Authentication failed. Check your BitBucket access token and permissions.")
                else:
                    raise Exception(f"Failed to fetch file '{file_path}': {e}")
            else:
                raise Exception(f"Error fetching file '{file_path}': {e}")

    def list_files(self, directory_path: str = "", file_extension: str = ".prompt") -> list[str]:
        """
        List files in a directory with a specific extension.

        Args:
            directory_path: Directory path in the repository (empty for root)
            file_extension: File extension to filter by (default: .prompt)

        Returns:
            List of file paths
        """
        safe_dir: Final = _sanitize_file_path(directory_path) if directory_path else ""
        url: Final = f"{self.base_url}/repositories/{self.workspace}/{self.repository}/src/{self.branch}/{safe_dir}"

View on GitHub (pinned to 77b7c6c40c)