{"record":{"id":"627cd213dc7cba13","repo":"ComposioHQ/composio","slug":"failed-to-download-file-response-status-resp","errorCode":null,"errorMessage":"Failed to download file: ${response.status} ${response.statusText}","messagePattern":"Failed to download file: (.+?) (.+?)","errorType":"exception","errorClass":"RemoteFileDownloadError","httpStatus":null,"severity":"error","filePath":"ts/packages/core/src/models/RemoteFile.ts","lineNumber":91,"sourceCode":"  }\n\n  /** Filename extracted from the mount path (e.g. \"report.pdf\" from \"output/report.pdf\") */\n  get filename(): string {\n    return platform.basename(this.mountRelativePath);\n  }\n\n  /**\n   * Fetches the file content as a buffer.\n   * @returns The file content as a Uint8Array\n   * @throws RemoteFileDownloadError if the fetch fails\n   */\n  async buffer(): Promise<Uint8Array> {\n    // SSRF guard: `downloadUrl` is set from an API response, so it is untrusted\n    // input like every other response field, and its bytes are handed straight\n    // back to the caller. See ssrfGuard.node.ts.\n    const response = await ssrfSafeFetchWhereSupported(this.downloadUrl);\n    if (!response.ok) {\n      throw new RemoteFileDownloadError(\n        `Failed to download file: ${response.status} ${response.statusText}`,\n        {\n          statusCode: response.status,\n          statusText: response.statusText,\n          downloadUrl: this.downloadUrl,\n          mountRelativePath: this.mountRelativePath,\n          filename: this.filename,\n          cause: new Error(`HTTP ${response.status}: ${response.statusText}`),\n        }\n      );\n    }\n    const arrayBuffer = await response.arrayBuffer();\n    return new Uint8Array(arrayBuffer);\n  }\n\n  /**\n   * Fetches the file content as UTF-8 text.\n   * @returns The file content as a string","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/ComposioHQ/composio/blob/64b1b85502b1beeb2379e6c9e8bf1104504fa637/ts/packages/core/src/models/RemoteFile.ts#L73-L109","documentation":"The HTTP fetch of the file's downloadUrl returned a non-2xx status, and RemoteFile.buffer() wraps it in RemoteFileDownloadError with status, statusText, downloadUrl and mountRelativePath attached. The URL is fetched through an SSRF-safe wrapper because it comes from an API response.","triggerScenarios":"Calling await remoteFile.buffer() (or any API that internally reads bytes) when the pre-signed download URL has expired, the file was deleted server-side, or the file host returns 403/404/5xx.","commonSituations":"Holding a RemoteFile for a long time before downloading, letting the signed URL expire; files removed by retention policy; storage provider outages; region-restricted access.","solutions":["Retry with a freshly fetched RemoteFile (re-list or re-fetch the parent resource to get a new signed URL)","Check err.statusCode on RemoteFileDownloadError: 403/410 usually means expired link, 404 deleted file","If 5xx, retry with backoff after re-fetching the file record"],"exampleFix":"// before\nconst bytes = await file.buffer(); // stale signed URL\n// after\nconst fresh = await composio.sessions.get(sessionId).files.get(fileId);\nconst bytes = await fresh.buffer();","handlingStrategy":"retry","validationCode":"if (!file.downloadUrl) throw new Error('no download URL; refetch file record');","typeGuard":null,"tryCatchPattern":"try { const bytes = await file.buffer(); } catch (e) {\n  if (e instanceof RemoteFileDownloadError) {\n    if ([403, 404, 410].includes(e.statusCode)) { const fresh = await refetchFile(file.id); return fresh.buffer(); }\n    await sleep(backoff); return file.buffer();\n  }\n  throw e;\n}","preventionTips":["Download promptly after fetching a RemoteFile","Refetch the file record to refresh signed URLs instead of caching them","Branch on statusCode to distinguish expiry from transient failures"],"tags":["files","download","http-status","ssrf"],"backgroundTag":"presigned-url-expired","analyzedSha":"64b1b85502b1beeb2379e6c9e8bf1104504fa637","analyzedAt":"2026-08-28T15:39:33.623Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}