ComposioHQ/composio · error · RemoteFileDownloadError

Failed to download file: ${response.status} ${response.statu

Error message

Failed to download file: ${response.status} ${response.statusText}

What it means

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.

Source

Thrown at ts/packages/core/src/models/RemoteFile.ts:91

  }

  /** Filename extracted from the mount path (e.g. "report.pdf" from "output/report.pdf") */
  get filename(): string {
    return platform.basename(this.mountRelativePath);
  }

  /**
   * Fetches the file content as a buffer.
   * @returns The file content as a Uint8Array
   * @throws RemoteFileDownloadError if the fetch fails
   */
  async buffer(): Promise<Uint8Array> {
    // SSRF guard: `downloadUrl` is set from an API response, so it is untrusted
    // input like every other response field, and its bytes are handed straight
    // back to the caller. See ssrfGuard.node.ts.
    const response = await ssrfSafeFetchWhereSupported(this.downloadUrl);
    if (!response.ok) {
      throw new RemoteFileDownloadError(
        `Failed to download file: ${response.status} ${response.statusText}`,
        {
          statusCode: response.status,
          statusText: response.statusText,
          downloadUrl: this.downloadUrl,
          mountRelativePath: this.mountRelativePath,
          filename: this.filename,
          cause: new Error(`HTTP ${response.status}: ${response.statusText}`),
        }
      );
    }
    const arrayBuffer = await response.arrayBuffer();
    return new Uint8Array(arrayBuffer);
  }

  /**
   * Fetches the file content as UTF-8 text.
   * @returns The file content as a string

View on GitHub (pinned to 64b1b85502)

Solutions

  1. Retry with a freshly fetched RemoteFile (re-list or re-fetch the parent resource to get a new signed URL)
  2. Check err.statusCode on RemoteFileDownloadError: 403/410 usually means expired link, 404 deleted file
  3. If 5xx, retry with backoff after re-fetching the file record

Example fix

// before
const bytes = await file.buffer(); // stale signed URL
// after
const fresh = await composio.sessions.get(sessionId).files.get(fileId);
const bytes = await fresh.buffer();
Defensive patterns

Strategy: retry

Validate before calling

if (!file.downloadUrl) throw new Error('no download URL; refetch file record');

Try / catch

try { const bytes = await file.buffer(); } catch (e) {
  if (e instanceof RemoteFileDownloadError) {
    if ([403, 404, 410].includes(e.statusCode)) { const fresh = await refetchFile(file.id); return fresh.buffer(); }
    await sleep(backoff); return file.buffer();
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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