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

  1. Log the exact uploadURL value passed in and check it is an absolute https URL with query parameters (presigned signature).
  2. If it is empty, the caller skipped or corrupted the StartLocalExecution response — verify mapStartLocalExecutionResponse and the ArchiveUploadURL *string handling before the call.
  3. Validate the URL with url.ParseRequestURI and a scheme check before calling UploadArchive (see defense code).
  4. 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

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)