ComposioHQ/composio · error · Error

Failed to download file: ${response.statusText}

Error message

Failed to download file: ${response.statusText}

What it means

downloadFileFromS3 fetches the s3Url from a tool-execution response via ssrfSafeFetch; a non-OK status throws with the status text. The body would be streamed to disk, so it is size-limited but the error path occurs before that.

Source

Thrown at ts/packages/core/src/utils/fileUtils.node.ts:365

  mimeType: string;
  /**
   * Absolute path to the directory to save the file into. When omitted, falls
   * back to `<home>/.composio/files` (the legacy default).
   */
  fileDownloadDir?: string;
  signal?: AbortSignal;
  /**
   * Maximum number of bytes to read from the response before rejecting.
   * Defaults to {@link MAX_URL_UPLOAD_SIZE_BYTES} (100 MiB).
   */
  maxDownloadBytes?: number;
}): Promise<FileDownloadData> => {
  // SSRF guard: `s3Url` is a field of the tool-execution response, so it is no
  // more trusted than a user-supplied URL — and the bytes it returns are
  // written to disk. See ssrfGuard.node.ts.
  const response = await ssrfSafeFetch(s3Url, { signal });
  if (!response.ok) {
    throw new Error(`Failed to download file: ${response.statusText}`);
  }
  // The response is attacker-influencable and streamed to disk, so the body is
  // read through the shared size guard rather than buffered wholesale.
  const data = await readResponseBodyWithLimit(response, maxDownloadBytes);

  const extension = getExtensionFromMimeType(mimeType);
  const fileName = generateTimestampedFilename(extension, `${toolSlug}_`);
  const filePath = saveFile(fileName, data, {
    isTempFile: fileDownloadDir === undefined,
    outputDir: fileDownloadDir,
  });
  return {
    name: fileName,
    mimeType: mimeType,
    s3Url: s3Url,

    /**
     * @todo: fix in follow-up PR.

View on GitHub (pinned to 64b1b85502)

Solutions

  1. Re-execute the tool or re-request the download URL to get a fresh signed link.
  2. Retry with backoff for transient 5xx.
  3. Inspect the s3Url manually with curl to see the exact status.
Defensive patterns

Strategy: retry

Try / catch

try { await downloadFileFromS3(url, {...}); } catch (e) {
  if ((e as Error).message.startsWith('Failed to download file:')) { /* refresh url / re-execute tool */ }
}

Prevention

When it happens

Trigger: A tool execution returns an s3Url that is expired (403 S3 signature), deleted (404), or the host returns 5xx during file download.

Common situations: Downloading tool output files long after execution finished, cross-region S3 issues, or backend-generated URLs with short TTLs.

Related errors


AI-assisted analysis of ComposioHQ/composio@64b1b85502 (2026-08-28). Data as JSON: /api/errors/038c16ecf5cad900. Report an issue: GitHub.