mastra-ai/mastra · critical · Error
Artifact upload failed: ${uploadResp.status} ${uploadResp.st
Error message
Artifact upload failed: ${uploadResp.status} ${uploadResp.statusText} What it means
After obtaining the presigned uploadUrl, uploadDeploy PUTs the zip artifact to it (expecting Content-Type application/zip). If the storage endpoint responds with a non-2xx status, the command throws with the HTTP status and status text, cancels the deploy record, and rethrows.
Source
Thrown at packages/cli/src/commands/studio/platform-api.ts:190
postCancel: c2 => c2.POST('/v1/studio/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/studio/deploys/{id}/upload-complete', { params: { path: { id } } }),
cancelDeploy: cancel,
client,
orgId,
});
return { id, status };
}
View on GitHub (pinned to 75dd419e61)
Solutions
- Retry `mastra studio deploy` — a fresh deploy gets a fresh presigned URL
- Reduce artifact size (prune node_modules, exclude dev assets) if the zip may exceed limits
- Check network/proxy rules allowing large PUTs to the storage host
- Upgrade the CLI in case the storage upload contract changed
- If the error persists with 403, report it — presigning may be broken server-side
Example fix
// before (retry on same URL not possible) mastra studio deploy --yes # Artifact upload failed: 403 Forbidden // after (new deploy -> new presigned URL) mastra studio deploy --yes # succeeds
Defensive patterns
Strategy: retry
Validate before calling
const MAX_ZIP = 100 * 1024 * 1024; // check platform limit
if (zipBuffer.byteLength > MAX_ZIP) {
throw new Error(`Artifact too large: ${zipBuffer.byteLength} bytes`);
}
if (!uploadUrl || new URL(uploadUrl).protocol !== 'https:') {
throw new Error('Invalid upload URL');
} Try / catch
try {
await uploadDeploy(zipBuffer);
} catch (e) {
if (e instanceof Error && e.message.startsWith('Artifact upload failed:')) {
console.error('Presigned PUT rejected; get a fresh URL by re-running deploy.');
process.exitCode = 3;
} else throw e;
} Prevention
- Always obtain a fresh presigned URL per attempt; never reuse upload URLs
- Keep deploy zips small (exclude dev dependencies and local assets)
- Ensure network/proxy allows large PUTs to the storage host
- Use stable, wired connections in CI to avoid mid-upload resets
When it happens
Trigger: The presigned PUT fails: expired/already-used upload URL, oversized zip exceeding storage limits, network interruption mid-upload, storage-side auth rejection, or wrong Content-Type negotiation with the storage backend.
Common situations: Large projects producing zips above the upload size limit; slow CI links timing out; retrying a deploy whose uploadUrl was already consumed; corporate proxies blocking large PUT requests to object storage.
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/544d7c8bd6facac3.
Report an issue: GitHub.