windmill-labs/windmill · error · Error
Invalid s3 object URI ${JSON.stringify(s3Object)}: expected
Error message
Invalid s3 object URI ${JSON.stringify(s3Object)}: expected s3://<storage>/<key> with a non-empty key (s3:///<key> for the default storage) What it means
parseS3Object accepts either an S3ObjectRecord object or an s3:// URI string. When given a string that starts with s3:// but does not match /^s3:\/\/([^/]*)\/(.+)$/ — i.e. an empty key like "s3://storage/" or "s3://" — it throws this error explaining the expected shape: s3://<storage>/<key>, with s3:///<key> meaning the default storage. It is distinct from the sibling error for strings that are not s3 URIs at all.
Source
Thrown at typescript-client/s3Types.ts:58
/** Use path-style URLs instead of virtual-hosted style */
pathStyle?: boolean;
};
/**
* Parse an S3 object from URI string or record format
* @param s3Object - S3 object as URI string (`s3://storage/key`, `s3:///key`
* for the default storage) or record. Any other string throws 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.
* @returns S3 object record with storage and s3 key
*/
export function parseS3Object(s3Object: S3Object): S3ObjectRecord {
if (typeof s3Object === "object") return s3Object;
const match = s3Object.match(/^s3:\/\/([^/]*)\/(.+)$/);
if (match) return { storage: match[1] || undefined, s3: match[2] };
if (s3Object.startsWith("s3://")) {
throw new Error(
`Invalid s3 object URI ${JSON.stringify(s3Object)}: expected s3://<storage>/<key> with a non-empty key (s3:///<key> for the default storage)`
);
}
throw new Error(
`Invalid s3 object ${JSON.stringify(s3Object)}: expected an s3://<storage>/<key> URI (e.g. "s3:///${s3Object}" for key "${s3Object}" in the default storage) or { s3: <key> }`
);
}
View on GitHub (pinned to e474e8803c)
Solutions
- Ensure the key portion after the storage segment is non-empty before calling: assert against the URI regex or check for a trailing slash.
- For the default storage use s3:///<key> — an empty storage segment is allowed, an empty key is not.
- Fix the source of the empty key (undefined variable, empty DB field) rather than defaulting silently.
- For bare keys, pass the plain key string or { s3: key }; that produces the sibling 'Invalid s3 object' message instead, which is clearer for non-URI inputs.
Example fix
// before
const uri = `s3://${storage}/${fileKey}`; // fileKey undefined -> "s3://myStorage/"
await s3Obj(uri); // throws
// after
if (!fileKey) throw new Error('fileKey is required');
const uri = `s3://${storage}/${fileKey}`;
await s3Obj(uri); // "s3://myStorage/report.csv" Defensive patterns
Strategy: validation
Validate before calling
function assertS3Uri(uri: string): void {
if (uri.startsWith('s3://')) {
const m = uri.match(/^s3:\/\/([^/]*)\/(.+)$/);
if (!m) throw new Error(`bad s3 URI (empty key?): ${uri}`);
}
} Type guard
const isS3ObjectUri = (v: unknown): v is `s3://${string}` =>
typeof v === 'string' && v.startsWith('s3://') && /^s3:\/\/([^/]*)\/.+$/.test(v); Try / catch
try {
await s3Obj(uri);
} catch (e) {
if (e.message.startsWith('Invalid s3 object URI')) {
await s3Obj({ s3: rawKey }); // fall back to object form / default storage
} else throw e;
} Prevention
- Check the key segment is non-empty before interpolating into s3:// URIs.
- Prefer { s3: key } object form to avoid URI-building bugs.
- Handle undefined variables that produce empty keys upstream.
- Remember s3:///<key> is the default-storage form — storage may be empty, the key may not.
When it happens
Trigger: Passing "s3://" or "s3://storage/" (empty key) to s3Obj, getPresignedS3PublicUrls, or any API resolving an S3Object; building the URI by concatenation where the key part is empty/undefined.
Common situations: Template interpolation `s3://${bucket}/${key}` with an undefined key; trailing-slash values from folder listings; copying a bucket root URI instead of an object key; upstream data returning empty key fields.
Related errors
- Invalid s3 object URI {s3_object!r}: expected s3://<storage>
- deleteS3File: s3 key is required
- Invalid s3 object ${JSON.stringify(s3Object)}: expected an s
- Invalid s3 object {s3_object!r}: expected an s3://<storage>/
- Invalid S3 mode format: {}
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/aeb07f693c0ae526.
Report an issue: GitHub.