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

  1. Retry `mastra studio deploy` — a fresh deploy gets a fresh presigned URL
  2. Reduce artifact size (prune node_modules, exclude dev assets) if the zip may exceed limits
  3. Check network/proxy rules allowing large PUTs to the storage host
  4. Upgrade the CLI in case the storage upload contract changed
  5. 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

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


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