apache/beam · error · S3ClientError
Tried to list nonexistent S3 path: s3://
Error message
Tried to list nonexistent S3 path: s3://%s/%s
What it means
`Client.list` raises this explicit `messages.S3ClientError` with HTTP code 404 when `list_objects_v2` succeeds but returns `KeyCount == 0`, i.e. the bucket+prefix matches no objects. The library treats an empty listing as 'the S3 path does not exist' to surface missing inputs early rather than silently returning zero files. Note it is also raised for prefixes that exist but are simply empty.
Solutions
- Verify the exact s3://bucket/prefix with `aws s3 ls s3://bucket/prefix` using the same credentials — if empty, the path truly has no objects.
- Fix the prefix string: check for missing trailing slash, wrong case, or wrong bucket/pipeline-option values.
- Ensure the upstream job/stage that writes the data completed successfully before listing.
- If an empty prefix is legitimate in your use case, catch `messages.S3ClientError` with code 404 and treat it as an empty result.
- Check lifecycle policies that may have expired the objects.
Example fix
// before
listing = client.list(messages.ListRequest(bucket='my-bucket', prefix='data/2026-09-12/'))
// after
try:
listing = client.list(messages.ListRequest(bucket='my-bucket', prefix='data/2026-09-12/'))
except messages.S3ClientError as e:
if e.code == 404:
listing = [] # no objects under prefix
else:
raise Defensive patterns
Strategy: try-catch
Validate before calling
import boto3
def prefix_has_objects(bucket, prefix, s3=None):
s3 = s3 or boto3.client('s3')
resp = s3.list_objects_v2(Bucket=bucket, Prefix=prefix, MaxKeys=1)
return resp.get('KeyCount', 0) > 0 Type guard
def is_missing_path(err): return isinstance(err, messages.S3ClientError) and getattr(err, 'code', None) == 404
Try / catch
try:
listing = client.list(request)
except messages.S3ClientError as e:
if getattr(e, 'code', None) == 404:
listing = [] # empty/missing prefix is acceptable
else:
raise Prevention
- Verify the s3://bucket/prefix exists with `aws s3 ls` before depending on it
- Check upstream jobs completed and wrote the expected output path
- Watch prefix string construction: trailing slash, case sensitivity, env-specific values
- Confirm lifecycle rules are not expiring objects early
- Treat empty prefixes explicitly in code instead of assuming existence
When it happens
Trigger: Calling `list(request)` where the given `bucket`/`prefix` combination has zero keys: the path was never written, was deleted, the prefix string is wrong (e.g. missing/extra slash), or the job's output path from a previous stage is misconfigured.
Common situations: Downstream job pointing at an upstream output path that never ran or failed; typos in the s3 path or prefix (case sensitivity, missing '/'); environment-specific config pointing to a test bucket; deleted/ lifecycle-expired objects.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
- Basepath %r must be S3 path.
- Invalid path
- Must provide one of client or options
- Path %r must be S3 path.
- All parts but the last must be larger than
AI-assisted analysis of apache/beam@12126d8942 (2026-09-13).
Data as JSON: /api/errors/2695ae3fdc17360d.
Report an issue: GitHub.
Appendix: source
Thrown at sdks/python/apache_beam/io/aws/clients/s3/boto3_client.py:185
Args:
request: (ListRequest) input message
Returns:
(ListResponse) The response message.
"""
kwargs = {'Bucket': request.bucket, 'Prefix': request.prefix}
if request.continuation_token is not None:
kwargs['ContinuationToken'] = request.continuation_token
try:
boto_response = self.client.list_objects_v2(**kwargs)
except Exception as e:
raise messages.S3ClientError(str(e), get_http_error_code(e))
if boto_response['KeyCount'] == 0:
message = 'Tried to list nonexistent S3 path: s3://%s/%s' % (
request.bucket, request.prefix)
raise messages.S3ClientError(message, 404)
items = [
messages.Item(
etag=content['ETag'],
key=content['Key'],
last_modified=content['LastModified'],
size=content['Size']) for content in boto_response['Contents']
]
try:
next_token = boto_response['NextContinuationToken']
except KeyError:
next_token = None
response = messages.ListResponse(items, next_token)
return response
def create_multipart_upload(self, request):View on GitHub (pinned to 12126d8942)