BerriAI/litellm · error · ValueError

bucket_name must be provided for GCS destination

Error message

bucket_name must be provided for GCS destination

What it means

FocusGCSDestination.__init__ raises this ValueError when the resolved config dict has no truthy 'bucket_name' key. The destination uploads serialized FOCUS billing exports to Google Cloud Storage, so a target bucket is mandatory; the check fails fast at construction instead of at first upload. Note that when the destination is built through FocusDestinationFactory, the factory raises its own 'FOCUS_GCS_BUCKET_NAME must be provided for GCS exports' first, so this exact message fires when FocusGCSDestination is instantiated directly.

Solutions

  1. Pass bucket_name in the destination config: FocusGCSDestination(prefix='focus', config={'bucket_name': 'my-bucket', 'service_account_json': '/path/to/sa.json'})
  2. Or set export FOCUS_GCS_BUCKET_NAME=my-bucket and construct via FocusDestinationFactory.create(provider='gcs', prefix='focus')
  3. Verify the bucket name is exact: no gs:// prefix, no trailing slash, correct project (gcloud storage buckets list)
  4. Also pass service_account_json (or FOCUS_GCS_PATH_SERVICE_ACCOUNT) if you do not rely on Application Default Credentials

Example fix

# before
FocusGCSDestination(prefix='focus', config={'bucket': 'litellm-focus'})

# after
FocusGCSDestination(prefix='focus', config={'bucket_name': 'litellm-focus'})
Defensive patterns

Strategy: validation

Validate before calling

import os

bucket = (destination_config or {}).get("bucket_name") or os.getenv("FOCUS_GCS_BUCKET_NAME")
if not bucket:
    raise SystemExit("Set FOCUS_GCS_BUCKET_NAME or destination_config['bucket_name'] before enabling the GCS FOCUS destination")

Type guard

from typing import Any
import os

def has_valid_gcs_bucket(config: dict[str, Any] | None) -> bool:
    """True when a non-empty GCS bucket is resolvable from config or env."""
    return bool((config or {}).get("bucket_name") or os.getenv("FOCUS_GCS_BUCKET_NAME"))

Try / catch

try:
    destination = FocusGCSDestination(prefix="focus", config=cfg)
except ValueError as e:
    if "bucket_name" in str(e):
        raise SystemExit(f"FOCUS GCS destination misconfigured: {e}") from e
    raise

Prevention

When it happens

Trigger: Calling FocusGCSDestination(prefix=..., config=...) with config=None, {}, or a dict where 'bucket_name' is missing, empty string, or None. Via the normal path, setting neither destination_config['bucket_name'] nor the FOCUS_GCS_BUCKET_NAME env var causes the factory's equivalent error before this line is reached.

Common situations: FOCUS_GCS_BUCKET_NAME not set in the deployment env, a typo'd config key (e.g. 'bucket' or 'gs://my-bucket' under a different key), YAML/env config where None-valued keys were filtered out before reaching the constructor, or direct instantiation in tests without a config.

Understand the failure class

Background: "X is required", "must be set", "cannot be empty": the missing-required-config error family, from Vertex AI project/location to WeChat keys — this error's family across 18 libraries.

Related errors


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

Appendix: source

Thrown at litellm/integrations/focus/destinations/gcs_destination.py:29

    encode_gcs_object_name_for_url,
)

from .base import FocusDestination, FocusTimeWindow


class FocusGCSDestination(GCSBucketBase, FocusDestination):
    """Upload serialized Focus exports to GCS using the GCS JSON API."""

    def __init__(
        self,
        *,
        prefix: str,
        config: dict[str, Any] | None = None,
    ) -> None:
        config = config or {}
        bucket_name: Final = config.get("bucket_name")
        if not bucket_name:
            raise ValueError("bucket_name must be provided for GCS destination")
        super().__init__(bucket_name=bucket_name)
        service_account_json: Final = config.get("service_account_json")
        if service_account_json is not None:
            self.path_service_account_json = service_account_json
        self.prefix = prefix.rstrip("/")

    async def deliver(
        self,
        *,
        content: bytes,
        time_window: FocusTimeWindow,
        filename: str,
    ) -> None:
        object_name: Final = self._build_object_key(time_window=time_window, filename=filename)
        headers: Final = await self.construct_request_headers(service_account_json=self.path_service_account_json)
        headers["Content-Type"] = "application/octet-stream"
        encoded_name: Final = encode_gcs_object_name_for_url(object_name)
        url: Final = (

View on GitHub (pinned to 77b7c6c40c)