{"record":{"id":"aeb07f693c0ae526","repo":"windmill-labs/windmill","slug":"invalid-s3-object-uri-json-stringify-s3object","errorCode":null,"errorMessage":"Invalid s3 object URI ${JSON.stringify(s3Object)}: 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":"Error","httpStatus":null,"severity":"error","filePath":"typescript-client/s3Types.ts","lineNumber":58,"sourceCode":"  /** Use path-style URLs instead of virtual-hosted style */\n  pathStyle?: boolean;\n};\n\n/**\n * Parse an S3 object from URI string or record format\n * @param s3Object - S3 object as URI string (`s3://storage/key`, `s3:///key`\n *   for the default storage) or record. Any other string throws rather than\n *   falling back to an auto-generated key: an auto key is requested by\n *   omitting the object, and a fallback would silently misplace the upload\n *   on any typo.\n * @returns S3 object record with storage and s3 key\n */\nexport function parseS3Object(s3Object: S3Object): S3ObjectRecord {\n  if (typeof s3Object === \"object\") return s3Object;\n  const match = s3Object.match(/^s3:\\/\\/([^/]*)\\/(.+)$/);\n  if (match) return { storage: match[1] || undefined, s3: match[2] };\n  if (s3Object.startsWith(\"s3://\")) {\n    throw new Error(\n      `Invalid s3 object URI ${JSON.stringify(s3Object)}: expected s3://<storage>/<key> with a non-empty key (s3:///<key> for the default storage)`\n    );\n  }\n  throw new Error(\n    `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> }`\n  );\n}\n","sourceCodeStart":40,"sourceCodeEnd":66,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/typescript-client/s3Types.ts#L40-L66","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst uri = `s3://${storage}/${fileKey}`; // fileKey undefined -> \"s3://myStorage/\"\nawait s3Obj(uri); // throws\n// after\nif (!fileKey) throw new Error('fileKey is required');\nconst uri = `s3://${storage}/${fileKey}`;\nawait s3Obj(uri); // \"s3://myStorage/report.csv\"","handlingStrategy":"validation","validationCode":"function assertS3Uri(uri: string): void {\n  if (uri.startsWith('s3://')) {\n    const m = uri.match(/^s3:\\/\\/([^/]*)\\/(.+)$/);\n    if (!m) throw new Error(`bad s3 URI (empty key?): ${uri}`);\n  }\n}","typeGuard":"const isS3ObjectUri = (v: unknown): v is `s3://${string}` =>\n  typeof v === 'string' && v.startsWith('s3://') && /^s3:\\/\\/([^/]*)\\/.+$/.test(v);","tryCatchPattern":"try {\n  await s3Obj(uri);\n} catch (e) {\n  if (e.message.startsWith('Invalid s3 object URI')) {\n    await s3Obj({ s3: rawKey }); // fall back to object form / default storage\n  } else throw e;\n}","preventionTips":["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."],"tags":["validation","s3","uri"],"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"}