{"record":{"id":"45d80c8940127bfd","repo":"grafana/k6","slug":"creating-upload-request-w","errorCode":null,"errorMessage":"creating upload request: %w","messagePattern":"creating upload request: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/cloudapi/provisioning/api.go","lineNumber":100,"sourceCode":"// LogsConfig holds the log-push configuration from the provisioning\n// API's runtime_config.logs object. tail_url is intentionally omitted:\n// --local-execution pushes logs, it doesn't tail them.\ntype LogsConfig struct {\n\tPushURL           string\n\tLevel             string\n\tLimit             int32\n\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))","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/grafana/k6/blob/01ffac6f245854c1b8adc6a69857c76a15f90022/internal/cloudapi/provisioning/api.go#L82-L118","documentation":"Returned by Client.UploadArchive (internal/cloudapi/provisioning/api.go:100) when http.NewRequestWithContext rejects the presigned S3 upload URL before any network I/O happens. NewRequestWithContext parses the URL, so this fires on syntactically invalid URLs (net/url.Parse error wrapped in %w), not on network or auth problems. In practice the URL comes from the provisioning API's archive_upload_url response field, so a hit almost always means that field was empty, truncated, or corrupted.","triggerScenarios":"Calling UploadArchive with uploadURL that is an empty string, has no scheme (e.g. \"bucket.s3.amazonaws.com/?sig=...\"), contains control characters or spaces, uses a scheme http.NewRequest does not build requests for (e.g. \"ftp://\"), or is a relative path.","commonSituations":"Backend/preview API returning a malformed or null-ish archive_upload_url that stringifies badly; a proxy or env rewriter (HTTP_PROXY mangling) altering the response; a test stubbing StartLocalExecution with a placeholder URL; passing res.ArchiveUploadURL without nil-checking.","solutions":["Log the exact uploadURL value passed in and check it is an absolute https URL with query parameters (presigned signature).","If it is empty, the caller skipped or corrupted the StartLocalExecution response — verify mapStartLocalExecutionResponse and the ArchiveUploadURL *string handling before the call.","Validate the URL with url.ParseRequestURI and a scheme check before calling UploadArchive (see defense code).","If the server genuinely returns a bad URL, re-run provisioning to obtain a fresh StartLocalExecutionResponse; if persistent, report against the k6 Cloud API."],"exampleFix":"// before\nerr := client.UploadArchive(ctx, resp.ArchiveUploadURL, archiveBytes)\n\n// after — nil-check and validate the presigned URL first\nif resp.ArchiveUploadURL == nil {\n\treturn errors.New(\"no archive upload URL returned by start_local_execution\")\n}\nu, err := url.ParseRequestURI(*resp.ArchiveUploadURL)\nif err != nil || (u.Scheme != \"http\" && u.Scheme != \"https\") {\n\treturn fmt.Errorf(\"invalid archive upload URL %q: %w\", *resp.ArchiveUploadURL, err)\n}\nerr = client.UploadArchive(ctx, *resp.ArchiveUploadURL, archiveBytes)","handlingStrategy":"validation","validationCode":"// Validate the presigned URL before attempting the PUT.\nfunc validUploadURL(raw string) bool {\n\tu, err := url.ParseRequestURI(raw)\n\tif err != nil {\n\t\treturn false\n\t}\n\treturn (u.Scheme == \"https\" || u.Scheme == \"http\") && u.Host != \"\" && u.RawQuery != \"\"\n}\n\nif resp.ArchiveUploadURL == nil || !validUploadURL(*resp.ArchiveUploadURL) {\n\treturn errors.New(\"start_local_execution returned no usable archive upload URL; re-run provisioning\")\n}","typeGuard":null,"tryCatchPattern":"if err := c.UploadArchive(ctx, uploadURL, body); err != nil {\n\tvar urlErr *url.Error\n\tif errors.As(err, &urlErr) && strings.Contains(urlErr.Err.Error(), \"missing protocol scheme\") {\n\t\t// malformed URL from server — re-provision instead of retrying the same value\n\t}\n}","preventionTips":["Always nil-check the ArchiveUploadURL *string from StartLocalExecution before dereferencing.","Never hand-edit or re-encode the presigned URL (query params are part of the signature).","Log uploadURL (redacting query) whenever constructing the upload to catch corruption early."],"tags":["k6","cloud","provisioning","s3","presigned-url","url-parse"],"backgroundTag":"invalid-url","analyzedSha":"01ffac6f245854c1b8adc6a69857c76a15f90022","analyzedAt":"2026-08-18T03:05:52.393Z","contentChangedAt":"2026-08-18T03:05:52.393Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}