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
- Verify the URL is reachable and returns 200 (curl -I the safeUrl in the error).
- Regenerate expired signed URLs (S3 presigned / GCS signed URLs) before the run.
- Fix storage permissions/CORS or make the object public/authenticated as needed.
- 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
- Regenerate presigned URLs shortly before each run; don't cache them across sessions.
- HEAD-check asset URLs before submitting them with agent messages.
- Ensure storage objects are readable by the runtime's credentials.
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
- Slack OAuth HTTP error: ${tokenResponse.status} ${tokenRespo
- Failed to stream background tasks: ${response.statusText}
- Failed to stream agent builder action: ${response.statusText
- Failed to observe agent builder action stream: ${response.st
- Failed to observe agent builder action stream legacy: ${resp
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/0f8d361726e9bff5.
Report an issue: GitHub.