windmill-labs/windmill · error · ValueError
Invalid s3 object {s3_object!r}: expected an s3://<storage>/
Error message
Invalid s3 object {s3_object!r}: expected an s3://<storage>/<key> URI (e.g. 's3:///{s3_object}' for key {s3_object!r} in the default storage) or S3Object(s3=<key>) What it means
The catch-all ValueError of the S3 object coercion helper: the value is neither a well-formed s3:// string URI nor an S3Object instance — e.g. a local path, a plain dict, an int, None, or an S3Object-like value from an older SDK version.
Source
Thrown at python-client/wmill/wmill/client.py:2331
def parse_s3_object(s3_object: S3Object | str) -> S3Object:
"""Parse S3 object from a `s3://<storage>/<key>` URI string (`s3:///<key>`
for the default storage) or S3Object format. Any other string raises
rather than falling back to an auto-generated key: an auto key is
requested by omitting the object, and a fallback would silently misplace
the upload on any typo.
"""
if isinstance(s3_object, str):
match = re.match(r'^s3://([^/]*)/(.+)$', s3_object)
if match:
return S3Object(s3=match.group(2), storage=match.group(1) or None)
if s3_object.startswith("s3://"):
raise ValueError(
f"Invalid s3 object URI {s3_object!r}: expected "
"s3://<storage>/<key> with a non-empty key "
"(s3:///<key> for the default storage)"
)
raise ValueError(
f"Invalid s3 object {s3_object!r}: expected an s3://<storage>/<key> "
f"URI (e.g. 's3:///{s3_object}' for key {s3_object!r} in the default "
"storage) or S3Object(s3=<key>)"
)
else:
return s3_object
def parse_variable_syntax(s: str) -> Optional[str]:
"""Parse variable syntax from string."""
if s.startswith("var://"):
return s[6:]
return None
def append_to_result_stream(text: str) -> None:
"""Append a text to the result stream.View on GitHub (pinned to e474e8803c)
Solutions
- Pass an S3Object instance: S3Object(s3='key', storage=None)
- Pass a valid URI string 's3://<storage>/<key>' or 's3:///<key>'
- Convert plain keys explicitly: S3Object(s3=my_key)
- Type-check inputs (and reject None/local paths) before calling
Example fix
// before
wm.read_s3_file("/tmp/data.csv") # local path, not an S3 object
// after
wm.read_s3_file("s3:///data.csv") # or S3Object(s3="data.csv") Defensive patterns
Strategy: type-guard
Validate before calling
import re
if not isinstance(s3_object, (str, S3Object)):
raise TypeError(f"Expected s3:// URI or S3Object, got {type(s3_object).__name__}") Type guard
def is_s3_object_like(v: object) -> bool:
import re
if isinstance(v, S3Object):
return True
return isinstance(v, str) and bool(re.match(r'^s3://([^/]*)/(.+)$', v)) Try / catch
try:
obj = coerce_s3_object(value)
except ValueError:
obj = S3Object(s3=str(value)) # only if value is a plain key Prevention
- Type-check at boundaries (job inputs, JSON payloads) before S3 calls
- Convert plain keys explicitly with S3Object(s3=key)
- Validate dicts coming from older SDK versions or external systems
- Reject None and local paths early with a clear error
When it happens
Trigger: Passing '/tmp/data.csv', {'s3': 'key'}, None, or any non-S3Object/non-URI value into functions accepting S3Object | str.
Common situations: Mixing local-file APIs with S3 APIs; constructing an object incorrectly after a library upgrade changed the S3Object type; passing values that are paths rather than s3:// URIs.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Type of file_content not supported
- Invalid s3 object URI {s3_object!r}: expected s3://<storage>
- result.substring(__RESULT_ERR_PREFIX.length)
- Flow modules must be an array
- Flow groups must be an array
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/e7c71ec97e46cdf6.
Report an issue: GitHub.