{"record":{"id":"793029139bb6bca8","repo":"ComposioHQ/composio","slug":"failed-to-upload-file-uploadresponse-statustext","errorCode":null,"errorMessage":"Failed to upload file: ${uploadResponse.statusText}","messagePattern":"Failed to upload file: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"ts/packages/core/src/models/ToolRouterSessionFileMount.ts","lineNumber":258,"sourceCode":"        ? (createUploadURLResponse as { body: unknown }).body\n        : createUploadURLResponse;\n\n    // SSRF guard: `upload_url` comes from the API response, so the target is\n    // validated before the file's bytes are sent to it — the same treatment the\n    // user-supplied URL above already gets. See ssrfGuard.node.ts.\n    const uploadResponse = await ssrfSafeFetchWhereSupported(\n      (uploadURLData as { upload_url: string }).upload_url,\n      {\n        method: 'PUT',\n        body: await fileToUpload.arrayBuffer(),\n        headers: {\n          'Content-Type': mimetype,\n        },\n      }\n    );\n\n    if (!uploadResponse.ok) {\n      throw new Error(`Failed to upload file: ${uploadResponse.statusText}`);\n    }\n\n    const createDownloadURLResponse = await this.client.toolRouter.session.files.createDownloadURL(\n      uploadOptions.data.mountId,\n      {\n        session_id: this.sessionId,\n        mount_relative_path: (uploadURLData as { mount_relative_path: string }).mount_relative_path,\n      }\n    );\n\n    const downloadData =\n      typeof createDownloadURLResponse === 'object' && 'body' in createDownloadURLResponse\n        ? (createDownloadURLResponse as { body: unknown }).body\n        : createDownloadURLResponse;\n\n    const parsed = RemoteFileDataSchema.safeParse(downloadData);\n    if (!parsed.success) {\n      throw new ValidationError('Failed to parse remote file properties', {","sourceCodeStart":240,"sourceCodeEnd":276,"githubUrl":"https://github.com/ComposioHQ/composio/blob/64b1b85502b1beeb2379e6c9e8bf1104504fa637/ts/packages/core/src/models/ToolRouterSessionFileMount.ts#L240-L276","documentation":"After obtaining a presigned upload URL, the SDK performs a raw fetch PUT of the file bytes. If the storage endpoint responds with a non-2xx status, a generic Error with the response statusText is thrown. This usually indicates the presigned URL was rejected, expired, or the request was malformed at the object-storage layer, not the Composio API layer.","triggerScenarios":"PUT to the presigned URL returns non-ok: expired presigned URL (long delay between createUploadURL and the PUT), Content-Type mismatch with the signed headers, network/proxy interference, or corrupted/empty body.","commonSituations":"Slow networks where the upload starts after URL expiry, corporate proxies rewriting requests, retries that reuse a stale presigned URL, or a mismatched mimetype between signing and upload.","solutions":["Retry the whole upload call so a fresh presigned URL is generated","Ensure mimetype passed at upload matches what the URL was signed for (do not modify headers)","Check network/proxy settings if uploads consistently fail"],"exampleFix":"// before\nconst remote = await mount.upload(file);\n// after\nlet remote;\nfor (let i = 0; i < 3; i++) {\n  try { remote = await mount.upload(file); break; }\n  catch (e) { if (i === 2) throw e; await new Promise(r => setTimeout(r, 1000 * (i + 1))); }\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { await mount.upload(file, opts); } catch (e) { if (e instanceof Error && e.message.startsWith('Failed to upload file:')) { /* retry with fresh URL */ } throw e; }","preventionTips":["Upload promptly after obtaining presigned URLs; don't cache them","Don't override Content-Type or add headers to presigned PUTs","Add exponential-backoff retry around uploads"],"tags":["network","upload","presigned-url","file-mount"],"backgroundTag":"presigned-url-upload-failed","analyzedSha":"64b1b85502b1beeb2379e6c9e8bf1104504fa637","analyzedAt":"2026-08-28T15:39:33.623Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}