From 431460838b62854bce9d4606a1d0cc08b3db8f33 Mon Sep 17 00:00:00 2001 From: mubashir1osmani Date: Wed, 9 Sep 2026 16:34:50 -0400 Subject: [PATCH 1/2] fix(s3): replace colons in generated log filenames Bedrock and Vertex AI batch file uploads use s3:// and gs:// URIs as response ids. The shared filename sanitizer replaced slashes but kept the scheme colon, producing log object keys that Hadoop-style consumers reject as a relative path in an absolute URI. Fixes #40234 --- litellm/integrations/s3.py | 2 +- tests/test_litellm/integrations/test_s3_v2.py | 32 +++++++++++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/litellm/integrations/s3.py b/litellm/integrations/s3.py index 8ce461eea5b..806b8e53675 100644 --- a/litellm/integrations/s3.py +++ b/litellm/integrations/s3.py @@ -250,7 +250,7 @@ def get_s3_object_key( start_time: datetime, s3_file_name: str, ) -> str: - sanitized_s3_file_name: Final = s3_file_name.replace("/", "_") + sanitized_s3_file_name: Final = s3_file_name.replace("/", "_").replace(":", "_") configured_prefix: Final = (s3_path.rstrip("/") + "/" if s3_path else "") + prefix date_segment: Final = start_time.strftime("%Y-%m-%d") + "/" # we need the s3 key to include the time, so we log cache hits too diff --git a/tests/test_litellm/integrations/test_s3_v2.py b/tests/test_litellm/integrations/test_s3_v2.py index a037284d7c1..680c4bcfe61 100644 --- a/tests/test_litellm/integrations/test_s3_v2.py +++ b/tests/test_litellm/integrations/test_s3_v2.py @@ -1129,9 +1129,11 @@ async def test_combined_prefix_reflects_in_s3_object_key(): assert "myteam/apikey/" in key, f"Expected both prefixes in key: {key}" -def test_s3_object_key_sanitizes_slashes_in_file_name(): +def test_s3_object_key_sanitizes_slashes_and_colons_in_file_name(): """Response ids containing slashes (e.g. bedrock batch job ARNs) must not - create nested S3 folders; only path/prefix/date slashes are separators.""" + create nested S3 folders, and colons must not survive into the filename: + Hadoop-style consumers reject a colon in the first segment of a relative + URI path (https://github.com/BerriAI/litellm/issues/40234).""" from litellm.integrations.s3 import get_s3_object_key start_time = datetime(2026, 2, 11, 0, 35, 18, 391582) @@ -1146,10 +1148,34 @@ def test_s3_object_key_sanitizes_slashes_in_file_name(): assert key == ( "LiteLLMAPPLogs/myteam/2026-02-11/" - "time-00-35-18-391582_arn:aws:bedrock:us-east-1:123456789012:model-invocation-job_gl18r6skk9yy.json" + "time-00-35-18-391582_arn_aws_bedrock_us-east-1_123456789012_model-invocation-job_gl18r6skk9yy.json" ) +@pytest.mark.parametrize( + "response_id", + [ + "s3://example-batch-bucket/litellm-bedrock-files/input.jsonl", + "gs://example-batch-bucket/litellm-vertex-files/input.jsonl", + ], +) +def test_s3_object_key_has_no_colon_for_cloud_uri_file_ids(response_id: str): + """Bedrock and Vertex batch file uploads use s3:// and gs:// URIs as + response ids; the generated log filename must be colon-free.""" + from litellm.integrations.s3 import get_s3_object_key + + key = get_s3_object_key( + s3_path="", + prefix="", + start_time=datetime(2026, 9, 7, 4, 51, 6, 685889), + s3_file_name=f"time-04-51-06-685889_{response_id}", + ) + + filename = key.rsplit("/", 1)[-1] + assert ":" not in filename + assert filename.endswith("_input.jsonl.json") + + def test_create_s3_batch_logging_element_flat_key_for_arn_response_id(): """End-to-end through the s3_v2 element builder: an ARN response id must yield a flat file directly under the date segment.""" From d3374c8a860e2e544fae598a6e0fb43efedc2e31 Mon Sep 17 00:00:00 2001 From: mubashir1osmani Date: Wed, 9 Sep 2026 16:43:20 -0400 Subject: [PATCH 2/2] test(s3): drop docstrings flagged by review --- tests/test_litellm/integrations/test_s3_v2.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/test_litellm/integrations/test_s3_v2.py b/tests/test_litellm/integrations/test_s3_v2.py index 680c4bcfe61..4bba8ae5f26 100644 --- a/tests/test_litellm/integrations/test_s3_v2.py +++ b/tests/test_litellm/integrations/test_s3_v2.py @@ -1130,10 +1130,6 @@ async def test_combined_prefix_reflects_in_s3_object_key(): def test_s3_object_key_sanitizes_slashes_and_colons_in_file_name(): - """Response ids containing slashes (e.g. bedrock batch job ARNs) must not - create nested S3 folders, and colons must not survive into the filename: - Hadoop-style consumers reject a colon in the first segment of a relative - URI path (https://github.com/BerriAI/litellm/issues/40234).""" from litellm.integrations.s3 import get_s3_object_key start_time = datetime(2026, 2, 11, 0, 35, 18, 391582) @@ -1160,8 +1156,6 @@ def test_s3_object_key_sanitizes_slashes_and_colons_in_file_name(): ], ) def test_s3_object_key_has_no_colon_for_cloud_uri_file_ids(response_id: str): - """Bedrock and Vertex batch file uploads use s3:// and gs:// URIs as - response ids; the generated log filename must be colon-free.""" from litellm.integrations.s3 import get_s3_object_key key = get_s3_object_key(