mastra-ai/mastra · error · MastraError

DOWNLOAD_ASSETS_FAILED

DOWNLOAD_ASSETS_FAILED

Error message

Failed to download asset: ${safeUrl}

What it means

downloadFromUrl fetches remote assets (with retry on 5xx) before prompt conversion. If the final response is still not ok (404, 403, etc.), it throws a MastraError with id DOWNLOAD_ASSETS_FAILED containing the safe URL in the message and details. The library refuses to silently embed a failed asset.

Source

Thrown at packages/core/src/agent/message-list/prompt/download-assets.ts:40

export const downloadFromUrl = async ({ url, downloadRetries }: { url: URL; downloadRetries: number }) => {
  const urlText = url.toString();
  const safeUrl = redactUrlForLog(url);

  try {
    const response = await fetchWithRetry(
      urlText,
      {
        method: 'GET',
      },
      downloadRetries,
      {
        shouldRetryResponse: response => response.status >= 500,
      },
    );

    if (!response.ok) {
      throw new MastraError({
        id: 'DOWNLOAD_ASSETS_FAILED',
        text: `Failed to download asset: ${safeUrl}`,
        domain: ErrorDomain.LLM,
        category: ErrorCategory.USER,
        details: { url: urlText },
      });
    }
    return {
      data: new Uint8Array(await response.arrayBuffer()),
      mediaType: response.headers.get('content-type') ?? undefined,
    };
  } catch (error) {
    throw new MastraError(
      {
        id: 'DOWNLOAD_ASSETS_FAILED',
        text: `Failed to download asset: ${safeUrl}`,
        domain: ErrorDomain.LLM,
        category: ErrorCategory.USER,

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Verify the URL is reachable and returns 200 (curl -I the safeUrl in the error).
  2. Regenerate expired signed URLs (S3 presigned / GCS signed URLs) before the run.
  3. Fix storage permissions/CORS or make the object public/authenticated as needed.
  4. Download the asset yourself and pass inline data/base64 content instead of a remote URL.

Example fix

// before
{ url: 'https://s3.amazonaws.com/bucket/img.png?X-Amz-Expires=60&...' } // expired
// after
const freshUrl = await getFreshPresignedUrl('bucket', 'img.png');
{ url: freshUrl }
Defensive patterns

Strategy: try-catch

Validate before calling

async function assertAssetReachable(url: string) {
  const res = await fetch(url, { method: 'HEAD' });
  if (!res.ok) throw new Error(`Asset ${url} not reachable: HTTP ${res.status}`);
}

Try / catch

try {
  assets = await downloadAssets([{ url }]);
} catch (e) {
  if (e instanceof MastraError && e.id === 'DOWNLOAD_ASSETS_FAILED') {
    console.error(`Asset fetch failed for ${e.detail?.url}; regenerating signed URL`);
    assets = await downloadAssets([{ url: await refreshPresignedUrl(e.detail.url) }]);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling downloadAssets (or the agent flow that does so) with an attachment/image URL that responds with a non-2xx, non-retryable status — expired signed S3/GCS URLs, removed files, private buckets, DNS-level failures surfacing as !response.ok paths after the retried fetch.

Common situations: Expired presigned URLs (S3 SignatureDoesNotMatch / expired token); deleted or renamed CDN objects; hotlink-protected or geo-blocked assets; wrong storage permissions.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/0f8d361726e9bff5. Report an issue: GitHub.