grafana/k6 · error

upload archive

Error message

upload archive: %w

What it means

ProvisionLocalExecution could not PUT the serialized archive bytes to the presigned upload URL returned by StartLocalExecution (sleResp.ArchiveUploadURL). The wrapped error is either an HTTP failure from the object store (S3/GCS) or a transport error; the presigned URL is short-lived and bound to the declared ArchiveSize.

Solutions

  1. Retry provisioning end-to-end to get a fresh presigned URL — do not cache or reuse ArchiveUploadURL
  2. Make sure the exact same archiveBytes buffer produced during serialization is what gets uploaded (no second Write)
  3. Allow egress to the object-store domain reported in the error (often S3) in proxy/firewall rules
  4. Compress or shrink the archive (drop unused data files) if uploads routinely outlive the URL lifetime

Example fix

// before
archiveBytes = maybeRewritten // re-serialized or modified after size was computed
err := client.UploadArchive(ctx, *sleResp.ArchiveUploadURL, archiveBytes)

// after
// upload the identical buffer whose length was declared
err := client.UploadArchive(ctx, *sleResp.ArchiveUploadURL, archiveBytes)
Defensive patterns

Strategy: retry

Validate before calling

// guard the invariant: declared size must equal uploaded bytes
if sleResp.ArchiveUploadURL != nil && int64(len(archiveBytes)) != sleReq.ArchiveSize {
    return nil, fmt.Errorf("archive size drift: declared %d, have %d", sleReq.ArchiveSize, len(archiveBytes))
}

Try / catch

err := c.UploadArchive(ctx, *sleResp.ArchiveUploadURL, archiveBytes)
if err != nil {
    if isTransientOrExpired(err) {
        // re-run provisioning to obtain a fresh presigned URL; do not reuse it
        return nil, fmt.Errorf("upload archive (re-provision for new URL): %w", err)
    }
    return nil, fmt.Errorf("upload archive: %w", err)
}

Prevention

When it happens

Trigger: Presigned URL expired because too much time passed between StartLocalExecution and the upload, request signature mismatch when the uploaded body length differs from the declared ArchiveSize, network egress blocked to the object store domain, or the URL was already consumed.

Common situations: Slow links or large archives (>1 GB) exceeding URL validity, corporate firewalls allowing the API host but blocking *.amazonaws.com, or code that mutates/re-serializes the archive between computing archiveBytes and uploading it.

Related errors


AI-assisted analysis of grafana/k6@01ffac6f24 (2026-08-18). Data as JSON: /api/errors/e37cf801dbf09aab. Report an issue: GitHub.

Appendix: source

Thrown at internal/cloudapi/provisioning/provision.go:93

		archiveBytes = buf.Bytes()
	}

	sleReq := StartLocalExecutionRequest{
		Options:       params.Options,
		MaxVUs:        params.MaxVUs,
		TotalDuration: params.TotalDuration,
		ArchiveSize:   archiveSize,
	}

	sleResp, err := c.StartLocalExecution(ctx, loadTestID, sleReq)
	if err != nil {
		return nil, fmt.Errorf("start local execution: %w", err)
	}

	switch {
	case params.Archive != nil && sleResp.ArchiveUploadURL != nil:
		if err := c.UploadArchive(ctx, *sleResp.ArchiveUploadURL, archiveBytes); err != nil {
			return nil, fmt.Errorf("upload archive: %w", err)
		}
	case params.Archive != nil && sleResp.ArchiveUploadURL == nil:
		// We had an archive to upload but the API returned no upload URL;
		// proceed without uploading rather than failing the run.
		c.logger.Warn("archive present but provisioning API returned no upload URL; skipping archive upload")
	}

	if err := c.WaitForTestRunReady(ctx, sleResp.TestRunID, params.PollInterval); err != nil {
		return nil, fmt.Errorf("wait for test run ready: %w", err)
	}

	return &ProvisionResult{
		TestRunID:             sleResp.TestRunID,
		TestRunDetailsPageURL: sleResp.TestRunDetailsPageURL,
		RuntimeConfig:         sleResp.RuntimeConfig,
	}, nil
}

View on GitHub (pinned to 01ffac6f24)