grafana/k6 · error
creating upload request
Error message
creating upload request: %w
What it means
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.
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.
Example fix
// before
err := client.UploadArchive(ctx, resp.ArchiveUploadURL, archiveBytes)
// after — nil-check and validate the presigned URL first
if resp.ArchiveUploadURL == nil {
return errors.New("no archive upload URL returned by start_local_execution")
}
u, err := url.ParseRequestURI(*resp.ArchiveUploadURL)
if err != nil || (u.Scheme != "http" && u.Scheme != "https") {
return fmt.Errorf("invalid archive upload URL %q: %w", *resp.ArchiveUploadURL, err)
}
err = client.UploadArchive(ctx, *resp.ArchiveUploadURL, archiveBytes) Defensive patterns
Strategy: validation
Validate before calling
// Validate the presigned URL before attempting the PUT.
func validUploadURL(raw string) bool {
u, err := url.ParseRequestURI(raw)
if err != nil {
return false
}
return (u.Scheme == "https" || u.Scheme == "http") && u.Host != "" && u.RawQuery != ""
}
if resp.ArchiveUploadURL == nil || !validUploadURL(*resp.ArchiveUploadURL) {
return errors.New("start_local_execution returned no usable archive upload URL; re-run provisioning")
} Try / catch
if err := c.UploadArchive(ctx, uploadURL, body); err != nil {
var urlErr *url.Error
if errors.As(err, &urlErr) && strings.Contains(urlErr.Err.Error(), "missing protocol scheme") {
// malformed URL from server — re-provision instead of retrying the same value
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.
Related errors
AI-assisted analysis of grafana/k6@01ffac6f24 (2026-08-18).
Data as JSON: /api/errors/45d80c8940127bfd.
Report an issue: GitHub.
Appendix: source
Thrown at internal/cloudapi/provisioning/api.go:100
// LogsConfig holds the log-push configuration from the provisioning
// API's runtime_config.logs object. tail_url is intentionally omitted:
// --local-execution pushes logs, it doesn't tail them.
type LogsConfig struct {
PushURL string
Level string
Limit int32
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))View on GitHub (pinned to 01ffac6f24)