grafana/k6 · error

uploading archive

Error message

uploading archive: %w

What it means

Returned by Client.UploadArchive (internal/cloudapi/provisioning/api.go:107) when doWithRetry gives up on the PUT to the presigned S3 URL. doWithRetry already retries transport errors and 5xx responses, so seeing this error means the request never produced a usable HTTP response even after those retries: DNS resolution failure, connection refused/reset, TLS handshake failure, or a context cancellation/timeout. The 4xx responses (like an expired signature) do NOT produce this error — they surface as 'archive upload failed: %d %s'.

Solutions

  1. Inspect the wrapped error: a net.DNSError means DNS, a context.Canceled/DeadlineExceeded means the run context died, TLS x509 errors point at interception — fix that layer first.
  2. Verify egress to the S3 host in the URL (curl -I on scheme+host) from the same machine/container and proxy env.
  3. Retry the whole k6 run — a new StartLocalExecution issues a fresh presigned URL and a new upload attempt.
  4. For very large archives on slow links, raise the client timeout passed to provisioning.NewClient so the context outlives the upload.
  5. If S3 is returning persistent 5xx, wait and retry later; nothing client-side fixes it.

Example fix

// before
if err := c.UploadArchive(ctx, resp.ArchiveUploadURL, body); err != nil { return err }

// after — surface the cause class and retry once with a fresh presigned URL
err := c.UploadArchive(ctx, uploadURL, body)
if err != nil {
	var dnsErr *net.DNSError
	switch {
	case errors.As(err, &dnsErr):
		return fmt.Errorf("cannot resolve archive upload host %q: %w", dnsErr.Name, err)
	case errors.Is(err, context.DeadlineExceeded):
		return fmt.Errorf("archive upload timed out (archive size %d bytes): %w", len(body), err)
	}
}
Defensive patterns

Strategy: retry

Validate before calling

// Pre-flight reachability of the upload host (cheap TCP probe) before the PUT.
u, err := url.Parse(uploadURL)
if err != nil {
	return err
}
conn, err := net.DialTimeout("tcp", u.Host+portOf(u), 5*time.Second)
if err != nil {
	return fmt.Errorf("archive upload host unreachable: %w", err)
}
_ = conn.Close()

Try / catch

var err error
for attempt := 1; attempt <= 3; attempt++ {
	err = c.UploadArchive(ctx, uploadURL, body)
	if err == nil {
		break
	}
	if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
		break // retrying cannot help a dead context
	}
	// transport errors are worth retrying with a fresh URL
	uploadURL = refreshPresignedURL()
	time.Sleep(time.Duration(attempt) * 5 * time.Second)
}

Prevention

When it happens

Trigger: PUT to the presigned URL failing at transport level: no DNS for the S3 endpoint, firewall/proxy blocking the upload host, TLS interception breaking the handshake, request context cancelled (e.g. global timeout or Ctrl+C) mid-retry, or S3 returning only 5xx across all retry attempts.

Common situations: Corporate egress proxies blocking *.amazonaws.com; container with broken DNS; k6_CLOUD_TIMEOUT-style overall context too short for a large archive; transient S3 outage persisting longer than the retry budget; VPN half-open connections.

Related errors


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

Appendix: source

Thrown at internal/cloudapi/provisioning/api.go:107

	PushPeriodSeconds string
	MessageMaxSize    int32
	AllowedLabels     []string
}

// UploadArchive PUTs pre-serialised archive bytes to the given
// presigned S3 URL. The URL carries auth in query params, so no
// Authorization header is set. Retries on 5xx and transport errors.
func (c *Client) UploadArchive(ctx context.Context, uploadURL string, body []byte) error {
	req, err := http.NewRequestWithContext(ctx, http.MethodPut, uploadURL, bytes.NewReader(body))
	if err != nil {
		return fmt.Errorf("creating upload request: %w", err)
	}
	req.Header.Set("Content-Type", "application/x-tar")
	req.ContentLength = int64(len(body))

	resp, err := c.doWithRetry(req)
	if err != nil {
		return fmt.Errorf("uploading archive: %w", err)
	}
	defer func() {
		_, _ = io.Copy(io.Discard, resp.Body)
		_ = resp.Body.Close()
	}()

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		respBody, readErr := io.ReadAll(resp.Body)
		if readErr != nil || len(respBody) == 0 {
			return fmt.Errorf("archive upload failed: %d %s",
				resp.StatusCode, http.StatusText(resp.StatusCode))
		}
		return fmt.Errorf("archive upload failed: %d %s: %s",
			resp.StatusCode, http.StatusText(resp.StatusCode), respBody)
	}

	return nil
}

View on GitHub (pinned to 01ffac6f24)