windmill-labs/windmill · error · ValueError
Invalid s3 object URI {s3_object!r}: expected s3://<storage>
Error message
Invalid s3 object URI {s3_object!r}: expected s3://<storage>/<key> with a non-empty key (s3:///<key> for the default storage) What it means
Raised by the S3 object coercion helper when a string starts with s3:// but does not match s3://<storage>/<key> with a non-empty key — e.g. 's3://mybucket' (no key), 's3://mybucket/' (empty key), or a garbled URI. Valid forms are 's3://<storage>/<key>' or 's3:///<key>' for the default storage.
Source
Thrown at python-client/wmill/wmill/client.py:2326
if s.startswith("$res:"):
return s[5:]
if s.startswith("res://"):
return s[6:]
return None
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:]View on GitHub (pinned to e474e8803c)
Solutions
- Format the URI as s3://<storage>/<key>, e.g. 's3://mybucket/path/to/file.csv'
- Use s3:///<key> (empty storage) for the default storage
- Pass a plain key string or S3Object(s3=key, storage=...) instead of a hand-built URI
- Validate with regex ^s3://([^/]*)/(.+)$ before calling
Example fix
// before
wm.write_s3_file("s3://mybucket", data) # missing key
// after
wm.write_s3_file("s3://mybucket/reports/file.csv", data) Defensive patterns
Strategy: validation
Validate before calling
import re
def is_valid_s3_uri(u: object) -> bool:
return isinstance(u, str) and bool(re.match(r'^s3://([^/]*)/(.+)$', u)) Type guard
def is_s3_uri(u: object) -> bool:
import re
return isinstance(u, str) and bool(re.match(r'^s3://([^/]*)/(.+)$', u)) Try / catch
try:
obj = coerce_s3_object(uri)
except ValueError as e:
raise ValueError(f"Bad S3 URI {uri!r}; use s3://<storage>/<key>") from e Prevention
- Always include a non-empty key: s3://storage/key or s3:///key
- Validate URIs with regex ^s3://([^/]*)/(.+)$ before calls
- Prefer constructing S3Object(s3=key, storage=storage) over hand-built strings
- Don't confuse bucket names or local paths with s3:// URIs
When it happens
Trigger: Passing 's3://mybucket', 's3://mybucket/', or a double-slashed/truncated URI to any API accepting S3Object | str (write_s3_file, read_s3_file, delete_s3_object, etc.).
Common situations: Building the URI by string concatenation and dropping the key; confusing bucket name with a full URI; copying a URI and truncating the key part.
Related errors
- Invalid s3 object URI ${JSON.stringify(s3Object)}: expected
- Invalid s3 object {s3_object!r}: expected an s3://<storage>/
- deleteS3File: s3 key is required
- Invalid s3 object ${JSON.stringify(s3Object)}: expected an s
- path or hash_ must be provided
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/011ee52ec1d0cb65.
Report an issue: GitHub.