grafana/k6 · error

creating upload request: %w

Error message

creating upload request: %w

What it means

UploadArchive (internal/cloudapi/provisioning/api.go:97-101) starts by building an http PUT request to the presigned upload URL the provisioning backend returned. http.NewRequestWithContext fails only for a malformed URL (control characters, invalid port, or a URL that cannot be parsed at all), so this error means the archive upload URL itself is not a valid absolute URL - nothing was sent over the network yet.

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 93accf6570)

Solutions

  1. Print/inspect the exact upload URL the run received (enable debug logging) and compare it with what the backend issued
  2. Fix the provisioning service or mock to return a full absolute https URL including query parameters
  3. Sanitize env values in CI (strip quotes, join wrapped lines) before launching k6

Example fix

# before - wrapped line in .env silently adds a newline
K6_ARCHIVE_UPLOAD_URL=https://bucket.s3.amazonaws.com/...
?X-Amz-Signature=abc123

# after - single line, quoted
K6_ARCHIVE_UPLOAD_URL='https://bucket.s3.amazonaws.com/...?X-Amz-Signature=abc123'
Defensive patterns

Strategy: validation

Validate before calling

# orchestrators: validate the upload URL is absolute before invoking k6
python3 - <<'EOF'
from urllib.parse import urlparse
import os, sys
u = os.environ.get('ARCHIVE_UPLOAD_URL', '')
p = urlparse(u)
assert p.scheme in ('http', 'https') and p.netloc, f'not an absolute URL: {u!r}'
assert '\n' not in u and ' ' not in u, 'upload URL contains whitespace'
EOF

Prevention

When it happens

Trigger: ArchiveUploadURL from the start_local_execution response (or the orchestrator injecting it) being a relative path, a template placeholder that was never filled, a value with embedded newlines/spaces from CI env mangling, or a truncated copy-paste.

Common situations: Backend or mock returning '/upload/abc' instead of 'https://bucket.../upload?X-Amz-Signature=...'; secrets/CI systems wrapping values in quotes that end up literal; line-wrapped env files inserting newlines into long presigned URLs.

Related errors


AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15). Data as JSON: /api/errors/45d80c8940127bfd. Report an issue: GitHub.