{"record":{"id":"2530e07cdf478d87","repo":"ComposioHQ/composio","slug":"failed-to-upload-file-to-s3-uploadresponse-stat-2530e0","errorCode":null,"errorMessage":"Failed to upload file to S3: ${uploadResponse.statusText}","messagePattern":"Failed to upload file to S3: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"ts/packages/core/src/utils/fileUtils.node.ts","lineNumber":262,"sourceCode":"  // Note: do not set `Content-Length` manually. Node.js's built-in fetch (undici)\n  // computes it from `body` and rejects any user-supplied value with\n  // `InvalidArgumentError: invalid content-length header`, which surfaces as\n  // `TypeError: fetch failed` and breaks every `file_uploadable` connector tool\n  // on Node 22+.\n  // SSRF guard: `new_presigned_url` comes from the API response, which the SDK\n  // treats as untrusted — a response naming an internal address would otherwise\n  // have the file's bytes PUT to it. See ssrfGuard.node.ts.\n  const uploadResponse = await ssrfSafeFetch(signedURL, {\n    method: 'PUT',\n    body: uploadBuffer,\n    signal,\n    headers: {\n      'Content-Type': mimeType,\n    },\n  });\n\n  if (!uploadResponse.ok) {\n    throw new Error(`Failed to upload file to S3: ${uploadResponse.statusText}`);\n  }\n\n  return key;\n};\n\nconst readFile = async (\n  file: File | string,\n  signal?: AbortSignal\n): Promise<{ fileName: string; content: string; mimeType: string }> => {\n  if (file instanceof File) {\n    const content = await file.arrayBuffer();\n    return {\n      fileName: file.name,\n      content: uint8ArrayToBase64(new Uint8Array(content)),\n      mimeType: file.type,\n    };\n  } else if (typeof file === 'string') {\n    if (isHttpUrl(file)) {","sourceCodeStart":244,"sourceCodeEnd":280,"githubUrl":"https://github.com/ComposioHQ/composio/blob/64b1b85502b1beeb2379e6c9e8bf1104504fa637/ts/packages/core/src/utils/fileUtils.node.ts#L244-L280","documentation":"Thrown by uploadFileToS3 when the PUT/POST to the pre-signed S3 upload URL returned by Composio returns a non-OK status. The upload URL is obtained from the backend; only statusText is reported.","triggerScenarios":"Uploading a file where the pre-signed URL has expired (403), the Content-Type differs from what was signed, the body size exceeds the signed limit, or S3 returns 5xx.","commonSituations":"Long delays between getting the upload URL and performing the upload, mismatched MIME type, network proxies stripping headers, transient S3 errors.","solutions":["Retry the whole upload flow so a fresh pre-signed URL is minted.","Ensure the mimeType passed to the SDK matches the actual content type used when the URL was requested.","Check for corporate proxies/VPVs interfering with PUT requests to S3."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { await upload(...); } catch (e) {\n  if ((e as Error).message.startsWith('Failed to upload file to S3')) { /* retry whole flow for fresh presigned URL */ }\n}","preventionTips":["Upload promptly after requesting the presigned URL.","Keep Content-Type consistent between URL request and PUT."],"tags":["s3","upload","network","typescript"],"backgroundTag":"s3-upload-failed","analyzedSha":"64b1b85502b1beeb2379e6c9e8bf1104504fa637","analyzedAt":"2026-08-28T15:39:33.623Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}