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

  1. Retry the deploy (a fresh signed URL is issued each run)
  2. Reduce artifact size (prune node_modules/unused assets before packaging, check the zip contents)
  3. Check proxy/firewall allows large PUT uploads to the storage host
  4. 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

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


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