grafana/k6 · error
uploading archive: %w
Error message
uploading archive: %w
What it means
After building the PUT, UploadArchive sends it through doWithRetry (internal/cloudapi/provisioning/api.go:103-107), which retries transport errors and 5xx responses and then gives up. This wrapped error means the request failed at the transport level or kept getting 5xx from the object storage - the presigned URL endpoint was not successfully reached within the retry budget. Auth travels in the URL query string, so intermediaries that alter the URL are a prime suspect.
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 93accf6570)
Solutions
- Test raw reachability from the same environment: 'curl -v --upload-file archive.tar "<presigned-url>"' and compare failures
- Add the object-storage/upload domain to the egress allowlist (it differs from the API host)
- Ensure proxies do not rewrite or truncate the URL query string - presigned auth breaks if any param changes
- Retry the run: presigned URLs expire, and a fresh run gets a fresh URL
Example fix
# before - CI allows only the API host allow: [api.cloud.k6.io:443] # after - also allow the archive upload host printed in the URL allow: [api.cloud.k6.io:443, *.storage.googleapis.com:443]
Defensive patterns
Strategy: retry
Validate before calling
# verify the storage host is reachable before the run
curl -sS -o /dev/null -w '%{http_code}\n' "$(echo "$ARCHIVE_UPLOAD_URL" | cut -d? -f1)" || echo 'upload host unreachable from this environment' Try / catch
if err := client.UploadArchive(ctx, url, body); err != nil {
if strings.Contains(err.Error(), "uploading archive") {
// transport/5xx failure after retries: retry the whole provisioning -
// a new run gets a fresh presigned URL, which also sidesteps expiry
}
} Prevention
- Allowlist the object-storage domain (distinct from the API host) in egress firewalls
- Ensure intermediaries preserve the full query string - presigned auth breaks on any alteration
- Pre-test with curl --upload-file from the same environment when onboarding a new CI runner
When it happens
Trigger: Egress firewall/proxy blocking the S3/object-storage domain; a proxy truncating the long presigned query string; DNS or TLS failures to the bucket host; connection resets or timeouts on large archive uploads; sustained 5xx from the storage service.
Common situations: CI runners with strict egress allowlists that only permit the API host and not the storage host; corporate proxies with URL length limits that strip signature parameters; slow links timing out while uploading multi-MB archives.
Related errors
- archive upload failed: %d %s
- archive upload failed: %d %s: %s
- upload archive: %w
- missing presigned url in response body
- unexpected HTTP error from %s: %d %s
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/31183ed82dc6c602.
Report an issue: GitHub.