ComposioHQ/composio · error · ToolFileUploadError

Failed to fetch file: ${response.statusText}

Error message

Failed to fetch file: ${response.statusText}

What it means

When an upload source is a URL, readFileFromUrl fetches it and throws ToolFileUploadError with reason 'source-fetch' if the HTTP response is not ok, embedding the statusText (e.g. 'Not Found', 'Forbidden').

Source

Thrown at ts/packages/cli/src/services/tool-file-uploads.ts:148

        isSchemaRecord(item) ? findFileUploadablePaths(item, basePath) : []
      )
    : isSchemaRecord(schema.items)
      ? findFileUploadablePaths(schema.items, basePath)
      : [];

  const seen = new Set<string>();
  return [...directPropertyPaths, ...variantPaths, ...itemPaths].filter(pathParts => {
    const key = pathParts.join('.');
    if (seen.has(key)) return false;
    seen.add(key);
    return true;
  });
};

const readFileFromUrl = async (path: Path.Path, url: string) => {
  const response = await fetch(url);
  if (!response.ok) {
    throw new ToolFileUploadError({
      message: `Failed to fetch file: ${response.statusText}`,
      reason: 'source-fetch',
      status: response.status,
    });
  }

  const bytes = new Uint8Array(await response.arrayBuffer());
  const parsedUrl = new URL(url);
  const fileName = path.basename(parsedUrl.pathname) || `file-${Date.now()}`;

  return {
    bytes,
    fileName,
    mimeType: response.headers.get('content-type') || 'application/octet-stream',
  };
};

// Runs the FileSystem read to completion for the plain-async pipeline above.

View on GitHub (pinned to 64b1b85502)

Solutions

  1. Verify the URL opens in a browser/curl with the same anonymity (no auth)
  2. If the link is presigned and expired, generate a fresh one
  3. Add retry with backoff for transient 5xx before starting the upload flow
  4. Download manually and pass the local file path instead of the URL

Example fix

# before
composio ... --file https://example.com/expired-presigned-url
# after
curl -I <url>   # confirm 200
composio ... --file ./downloaded-file.pdf
Defensive patterns

Strategy: retry

Validate before calling

const ok = await fetch(url, { method: 'HEAD' }).then(r => r.ok).catch(() => false);
if (!ok) throw new Error('source URL unreachable');

Try / catch

catch (e) { if (e instanceof ToolFileUploadError && e.reason === 'source-fetch') { await backoff(); retryUpload(); } throw e; }

Prevention

When it happens

Trigger: Passing a URL as the file source where the server returns 4xx/5xx: expired presigned link, private file requiring auth, 404 from a typo'd path, or a server-side 5xx.

Common situations: Presigned S3 URLs past their expiry, links requiring headers/cookies the plain fetch does not send, typo'd or deleted files, or transient upstream outages.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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