{"record":{"id":"76aabeb60f00e80b","repo":"transloadit/uppy","slug":"c-error-prefix-completemultipartupload-response","errorCode":null,"errorMessage":"${C.ERROR_PREFIX}CompleteMultipartUpload response missing Location or Key: ${JSON.stringify(r)}","messagePattern":"(.+?)CompleteMultipartUpload response missing Location or Key: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@uppy/aws-s3/src/s3-client/S3mini.ts","lineNumber":452,"sourceCode":"    if (parsed && typeof parsed === 'object') {\n      // Check for both cases (camelCase from our parser, PascalCase from S3)\n      const result =\n        parsed.completeMultipartUploadResult ||\n        parsed.CompleteMultipartUploadResult ||\n        parsed\n\n      if (result && typeof result === 'object') {\n        const r = result as Record<string, unknown>\n\n        // S3 returns PascalCase (Location, Bucket, Key, ETag).\n        // Normalize to lowercase for our type interface.\n        const resultLocation = (r.Location || r.location) as string | undefined\n        const resultBucket = (r.Bucket || r.bucket) as string | undefined\n        const resultKey = (r.Key || r.key) as string | undefined\n        const rawEtag = (r.ETag || r.eTag || r.etag) as string | undefined\n\n        if (!resultLocation || !resultKey) {\n          throw new Error(\n            `${C.ERROR_PREFIX}CompleteMultipartUpload response missing Location or Key: ${JSON.stringify(r)}`,\n          )\n        }\n\n        const etag = rawEtag ? U.sanitizeXmlETag(rawEtag) : undefined\n\n        return {\n          location: resultLocation,\n          bucket: resultBucket,\n          key: resultKey,\n          etag,\n        }\n      }\n    }\n\n    throw new Error(\n      `${C.ERROR_PREFIX}Failed to complete multipart upload: ${JSON.stringify(\n        parsed,","sourceCodeStart":434,"sourceCodeEnd":470,"githubUrl":"https://github.com/transloadit/uppy/blob/5d4dedd02a1ac0ae022c75c54aca76558f88e256/packages/@uppy/aws-s3/src/s3-client/S3mini.ts#L434-L470","documentation":"completeMultipartUpload parses S3's XML response; if the parsed result lacks Location and Key fields the client can't build a usable upload result, so it throws. This typically means the 200 response actually contained an error payload (S3 can return errors inside a 200 for this call).","triggerScenarios":"Completing a multipart upload where S3 returns 200 with an <Error> body (e.g. InternalError or slow part propagation), or an unexpected/empty response body that fails XML parsing into the expected fields.","commonSituations":"Completing immediately after uploading the last part; transient S3 internal errors; custom endpoints/proxies that mangle the XML response.","solutions":["Retry completeMultipartUpload after a short delay (S3 eventual consistency on part listing)","Log JSON.stringify(r) to see the actual payload; if it contains an Error code, address that (e.g. InvalidPart)","Ensure all parts and ETags were sent correctly in the complete request","Verify a standard S3-compatible endpoint is used and not mangling responses"],"exampleFix":"// before\nawait s3Mini.completeMultipartUpload({ key, uploadId, parts })\n\n// after\ntry {\n  await s3Mini.completeMultipartUpload({ key, uploadId, parts })\n} catch (err) {\n  if (String(err.message).includes('response missing Location or Key')) {\n    await delay(1000)\n    return s3Mini.completeMultipartUpload({ key, uploadId, parts })\n  }\n  throw err\n}","handlingStrategy":"retry","validationCode":"null","typeGuard":"null","tryCatchPattern":"try { await complete(...) } catch (e) { if (/response missing Location or Key/.test(String(e?.message))) { await delay(1000); return complete(...) } throw e }","preventionTips":["Retry completion once or twice with delay","Verify all ETags sent match those returned by uploadPart","Log the raw parsed response for diagnosis"],"tags":["s3","multipart","xml-parsing","retryable"],"backgroundTag":"s3-multipart-complete-failed","analyzedSha":"5d4dedd02a1ac0ae022c75c54aca76558f88e256","analyzedAt":"2026-08-28T12:18:41.267Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}