{"record":{"id":"011ee52ec1d0cb65","repo":"windmill-labs/windmill","slug":"invalid-s3-object-uri-s3-object-r-expected-s3","errorCode":null,"errorMessage":"Invalid s3 object URI {s3_object!r}: expected s3://<storage>/<key> with a non-empty key (s3:///<key> for the default storage)","messagePattern":"Invalid s3 object URI (.+?): expected s3://<storage>/<key> with a non-empty key \\(s3:///<key> for the default storage\\)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python-client/wmill/wmill/client.py","lineNumber":2326,"sourceCode":"    if s.startswith(\"$res:\"):\n        return s[5:]\n    if s.startswith(\"res://\"):\n        return s[6:]\n    return None\n\ndef parse_s3_object(s3_object: S3Object | str) -> S3Object:\n    \"\"\"Parse S3 object from a `s3://<storage>/<key>` URI string (`s3:///<key>`\n    for the default storage) or S3Object format. Any other string raises\n    rather than falling back to an auto-generated key: an auto key is\n    requested by omitting the object, and a fallback would silently misplace\n    the upload on any typo.\n    \"\"\"\n    if isinstance(s3_object, str):\n        match = re.match(r'^s3://([^/]*)/(.+)$', s3_object)\n        if match:\n            return S3Object(s3=match.group(2), storage=match.group(1) or None)\n        if s3_object.startswith(\"s3://\"):\n            raise ValueError(\n                f\"Invalid s3 object URI {s3_object!r}: expected \"\n                \"s3://<storage>/<key> with a non-empty key \"\n                \"(s3:///<key> for the default storage)\"\n            )\n        raise ValueError(\n            f\"Invalid s3 object {s3_object!r}: expected an s3://<storage>/<key> \"\n            f\"URI (e.g. 's3:///{s3_object}' for key {s3_object!r} in the default \"\n            \"storage) or S3Object(s3=<key>)\"\n        )\n    else:\n        return s3_object\n\n    \n\ndef parse_variable_syntax(s: str) -> Optional[str]:\n    \"\"\"Parse variable syntax from string.\"\"\"\n    if s.startswith(\"var://\"):\n        return s[6:]","sourceCodeStart":2308,"sourceCodeEnd":2344,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/python-client/wmill/wmill/client.py#L2308-L2344","documentation":"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.","triggerScenarios":"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.).","commonSituations":"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.","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"],"exampleFix":"// before\nwm.write_s3_file(\"s3://mybucket\", data)  # missing key\n// after\nwm.write_s3_file(\"s3://mybucket/reports/file.csv\", data)","handlingStrategy":"validation","validationCode":"import re\ndef is_valid_s3_uri(u: object) -> bool:\n    return isinstance(u, str) and bool(re.match(r'^s3://([^/]*)/(.+)$', u))","typeGuard":"def is_s3_uri(u: object) -> bool:\n    import re\n    return isinstance(u, str) and bool(re.match(r'^s3://([^/]*)/(.+)$', u))","tryCatchPattern":"try:\n    obj = coerce_s3_object(uri)\nexcept ValueError as e:\n    raise ValueError(f\"Bad S3 URI {uri!r}; use s3://<storage>/<key>\") from e","preventionTips":["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"],"tags":["s3","validation","uri","python"],"backgroundTag":"invalid-uri-format","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}