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

  1. Format the URI as s3://<storage>/<key>, e.g. 's3://mybucket/path/to/file.csv'
  2. Use s3:///<key> (empty storage) for the default storage
  3. Pass a plain key string or S3Object(s3=key, storage=...) instead of a hand-built URI
  4. 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

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


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/011ee52ec1d0cb65. Report an issue: GitHub.