mastra-ai/mastra · error
Artifact upload failed: ${uploadResp.status} ${uploadResp.st
Error message
Artifact upload failed: ${uploadResp.status} ${uploadResp.statusText} What it means
The CLI PUTs the zipped artifact to the signed uploadUrl (S3-style) and requires a 2xx. Any non-ok response is wrapped as 'Artifact upload failed: <status> <statusText>'. On this failure the deploy is best-effort cancelled so no orphaned pending deploy remains.
Source
Thrown at packages/cli/src/commands/server/platform-api.ts:175
postCancel: c2 => c2.POST('/v1/server/deploys/{id}/cancel', { params: { path: { id } } }),
client: c,
deployId: id,
});
// Step 2: Upload artifact to the signed URL
try {
if (uploadUrl.startsWith('file://')) {
const { writeFile } = await import('node:fs/promises');
const { fileURLToPath } = await import('node:url');
await writeFile(fileURLToPath(uploadUrl), Buffer.from(zipBuffer));
} else {
const uploadResp = await fetch(uploadUrl, {
method: 'PUT',
headers: { 'Content-Type': 'application/zip' },
body: new Uint8Array(zipBuffer),
});
if (!uploadResp.ok) {
throw new Error(`Artifact upload failed: ${uploadResp.status} ${uploadResp.statusText}`);
}
}
} catch (uploadError) {
await cancel(client);
throw uploadError;
}
// Step 3: Notify API that upload is complete → triggers build pipeline
await confirmUploadWithRetry({
postUploadComplete: c => c.POST('/v1/server/deploys/{id}/upload-complete', { params: { path: { id } } }),
cancelDeploy: cancel,
client,
orgId,
});
return { id, status };
}
View on GitHub (pinned to 75dd419e61)
Solutions
- Retry the deploy (a fresh signed URL is issued each run)
- Reduce artifact size (prune node_modules/unused assets before packaging, check the zip contents)
- Check proxy/firewall allows large PUT uploads to the storage host
- If 403/expire errors persist, investigate clock skew on the machine and platform-side storage permissions
Example fix
// before: uploading a bloated bundle zip -r bundle.zip . # includes node_modules, .git // after: package only the build output mastra build && mastra server deploy # CLI packages .mastra/output only
Defensive patterns
Strategy: retry
Validate before calling
// Pre-check artifact size before upload (avoid 413)
import { stat } from 'node:fs/promises';
const { size } = await stat(zipPath);
const MAX = 500 * 1024 * 1024;
if (size > MAX) throw new Error(`Artifact ${size} bytes exceeds ${MAX} — slim the bundle before deploying.`); Try / catch
try {
await deploy();
} catch (err) {
if (err instanceof Error && err.message.startsWith('Artifact upload failed:')) {
if (/\b(502|503|504)\b/.test(err.message)) {
await sleep(5000);
return deploy(); // transient gateway error — safe to retry, new URL issued
}
console.error('Upload rejected; check artifact size, URL expiry, and proxy rules:', err.message);
}
throw err;
} Prevention
- Deploy promptly after build so the signed URL doesn't expire
- Keep artifacts small; inspect the zip for accidental node_modules/.git inclusion
- Ensure proxies/firewalls permit large PUT uploads to the storage host
- Keep machine clocks synced (pre-signed URL validation)
When it happens
Trigger: Signed URL expired between deploy creation and upload (slow packaging, long gap); wrong Content-Type rejected by storage; network interruption or proxy blocking large PUT bodies; zip exceeds storage size limit (413); storage credentials/permission issues server-side (403).
Common situations: Uploading very large bundles over flaky corporate networks/VPN; CI runners with small egress limits hitting 413; long builds letting the pre-signed URL lapse (403); intercepting proxies returning 403/407.
Related errors
- Failed to upload artifact: ${uploadResp.statusText}
- Artifact upload failed: ${uploadResp.status} ${uploadResp.st
- Slack OAuth HTTP error: ${tokenResponse.status} ${tokenRespo
- Failed to stream background tasks: ${response.statusText}
- Failed to stream agent builder action: ${response.statusText
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/808c4271a1369cbd.
Report an issue: GitHub.