{"record":{"id":"31183ed82dc6c602","repo":"grafana/k6","slug":"uploading-archive-w","errorCode":null,"errorMessage":"uploading archive: %w","messagePattern":"uploading archive: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/cloudapi/provisioning/api.go","lineNumber":107,"sourceCode":"\tPushPeriodSeconds string\n\tMessageMaxSize    int32\n\tAllowedLabels     []string\n}\n\n// UploadArchive PUTs pre-serialised archive bytes to the given\n// presigned S3 URL. The URL carries auth in query params, so no\n// Authorization header is set. Retries on 5xx and transport errors.\nfunc (c *Client) UploadArchive(ctx context.Context, uploadURL string, body []byte) error {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPut, uploadURL, bytes.NewReader(body))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"creating upload request: %w\", err)\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/x-tar\")\n\treq.ContentLength = int64(len(body))\n\n\tresp, err := c.doWithRetry(req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"uploading archive: %w\", err)\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tif resp.StatusCode < 200 || resp.StatusCode >= 300 {\n\t\trespBody, readErr := io.ReadAll(resp.Body)\n\t\tif readErr != nil || len(respBody) == 0 {\n\t\t\treturn fmt.Errorf(\"archive upload failed: %d %s\",\n\t\t\t\tresp.StatusCode, http.StatusText(resp.StatusCode))\n\t\t}\n\t\treturn fmt.Errorf(\"archive upload failed: %d %s: %s\",\n\t\t\tresp.StatusCode, http.StatusText(resp.StatusCode), respBody)\n\t}\n\n\treturn nil\n}","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/cloudapi/provisioning/api.go#L89-L125","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before - CI allows only the API host\nallow: [api.cloud.k6.io:443]\n\n# after - also allow the archive upload host printed in the URL\nallow: [api.cloud.k6.io:443, *.storage.googleapis.com:443]","handlingStrategy":"retry","validationCode":"# verify the storage host is reachable before the run\ncurl -sS -o /dev/null -w '%{http_code}\\n' \"$(echo \"$ARCHIVE_UPLOAD_URL\" | cut -d? -f1)\" || echo 'upload host unreachable from this environment'","typeGuard":null,"tryCatchPattern":"if err := client.UploadArchive(ctx, url, body); err != nil {\n    if strings.Contains(err.Error(), \"uploading archive\") {\n        // transport/5xx failure after retries: retry the whole provisioning -\n        // a new run gets a fresh presigned URL, which also sidesteps expiry\n    }\n}","preventionTips":["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"],"tags":["cloud","network","upload","archive","s3","proxy"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}