BerriAI/litellm · error · RuntimeError
GCS upload failed: status={response.status_code} body={respo
Error message
GCS upload failed: status={response.status_code} body={response.text} What it means
After FocusGCSDestination.deliver() POSTs the export bytes to the GCS JSON API upload endpoint (uploadType=media), any response other than HTTP 200 raises RuntimeError embedding the status code and response body. Because the message is an f-string, the actual error contains the concrete status/body — common causes are 401/403 auth failures, 404 wrong bucket, and 400 malformed object name.
Source
Thrown at litellm/integrations/focus/destinations/gcs_destination.py:53
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 = (
f"https://storage.googleapis.com/upload/storage/v1/b/"
f"{self.BUCKET_NAME}/o?uploadType=media&name={encoded_name}"
)
response: Final = await self.async_httpx_client.post(url=url, headers=headers, data=content)
if response.status_code != 200:
raise RuntimeError(f"GCS upload failed: status={response.status_code} body={response.text}")
verbose_logger.debug(
"Focus GCS: uploaded %d bytes to gs://%s/%s",
len(content),
self.BUCKET_NAME,
object_name,
)
def _build_object_key(self, *, time_window: FocusTimeWindow, filename: str) -> str:
start_utc: Final = time_window.start_time.astimezone(timezone.utc)
date_component: Final = f"date={start_utc.strftime('%Y-%m-%d')}"
parts: Final = [self.prefix, date_component]
if time_window.frequency == "hourly":
parts.append(f"hour={start_utc.strftime('%H')}")
key_prefix: Final = "/".join(filter(None, parts))
return f"{key_prefix}/{filename}" if key_prefix else filename
View on GitHub (pinned to 6c2dcb801b)
Solutions
- Read the embedded status/body: 401/403 means credentials — verify FOCUS_GCS_PATH_SERVICE_ACCOUNT or ambient ADC, and grant roles/storage.objectCreator on the bucket.
- 404 means bucket mismatch — confirm the bucket_name used at construction matches an existing bucket in that project.
- Test auth independently: gcloud auth application-default print-access-token and a manual upload to the bucket.
- For network/VPC-SC errors (7xx/403 with 'Request is prohibited'), check egress rules and service perimeter configuration.
- Retry once on transient 5xx — GCS occasionally returns 500/503.
Example fix
# before
await destination.deliver(content=csv_bytes, time_window=tw, filename="focus.csv")
# after
try:
await destination.deliver(content=csv_bytes, time_window=tw, filename="focus.csv")
except RuntimeError as e:
log.error("GCS export failed: %s", e) # message includes status + body
raise Defensive patterns
Strategy: retry
Validate before calling
# Pre-flight: bucket exists and caller has objectCreator before first export
import subprocess
subprocess.run(["gcloud", "storage", "buckets", "describe", f"gs://{bucket_name}"], check=True) Try / catch
import re
async def safe_deliver(dest, **kw):
try:
return await dest.deliver(**kw)
except RuntimeError as e:
msg = str(e)
if re.search(r"status=(5\d\d|429)", msg):
await asyncio.sleep(5)
return await dest.deliver(**kw) # one retry on transient
raise # 401/403/404 are config issues — surface them Prevention
- Grant roles/storage.objectCreator to the exporting identity and verify with a test upload at deploy time.
- Alert on the embedded status code: repeated 403s indicate drift between configured SA and bucket IAM.
- Keep exports idempotent (same object key per window) so retries are safe.
When it happens
Trigger: Calling deliver() on a FocusGCSDestination when the service-account/default credentials lack storage.objects.create on the bucket (403), the bucket name is wrong or deleted (404), the OAuth token fetch failed (401), or the URL-encoded object name is rejected (400).
Common situations: Workload Identity not bound to the service account; service account JSON expired or for a different project; bucket deleted or renamed after config; VPC-SC perimeter blocking storage.googleapis.com; regional bucket access restrictions.
Related errors
- Mavvrik FOCUS destination: {label} must be HTTPS, got scheme
- Mavvrik FOCUS destination: GCS session init failed ({init_re
- Mavvrik FOCUS destination: GCS session init missing Location
- FOCUS_GCS_BUCKET_NAME must be provided for GCS exports
- bucket_name must be provided for GCS destination
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/87c8cba0c32a0e12.
Report an issue: GitHub.