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
- 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.
- Verify egress to the S3 host in the URL (curl -I on scheme+host) from the same machine/container and proxy env.
- Retry the whole k6 run — a new StartLocalExecution issues a fresh presigned URL and a new upload attempt.
- For very large archives on slow links, raise the client timeout passed to provisioning.NewClient so the context outlives the upload.
- 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
- Ensure egress to the S3 host in the presigned URL is allowed (proxy/firewall allowlist).
- Size the client timeout to the archive size and link speed before starting a run.
- Upload immediately after StartLocalExecution so the presigned window is fresh and the retry budget is intact.
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)