grafana/k6 · error

missing presigned url in response body

Error message

missing presigned url in response body

What it means

The browser file persister (file_persister.go) asks the Grafana Cloud (k6 Cloud) service for presigned upload URLs and decodes the JSON body into PresignedURLResponse. readPresignedURLResponse (file_persister.go:189-199) succeeded in decoding the JSON but rb.URLs was empty — the response is well-formed JSON yet contains no presigned urls, so there is no upload destination.

Solutions

  1. Verify the cloud credentials: K6_CLOUD_TOKEN (or k6 login) valid and scoped for the project
  2. Upgrade k6 to the latest version so the persister matches the current cloud API contract
  3. Capture the actual response body (proxy/debug logs, HTTPS_PROXY inspection) to see what the service really returned
  4. If cloud output is not intended, remove --out cloud / cloud env settings so no persister call happens
  5. If credentials and version check out, contact Grafana support — the service dropped urls for a valid request
Defensive patterns

Strategy: try-catch

Validate before calling

// Surface credential problems before the run
import http from 'k6/http';
const res = http.get(`${__ENV.K6_CLOUD_HOST}/v1/authorized`, {
  headers: { Authorization: `Bearer ${__ENV.K6_CLOUD_TOKEN}` },
});
if (res.status !== 200) throw new Error(`cloud auth failed: ${res.status}`);

Try / catch

// Persister errors abort cloud upload; catch at run level and re-check env
try {
  export const options = { cloud: { projectID: 123 } };
} catch (e) {
  if (/presigned url/.test(String(e))) {
    console.error('cloud returned no upload URLs — check K6_CLOUD_TOKEN and k6 version');
  }
}

Prevention

When it happens

Trigger: Running k6 with cloud output (--out cloud / cloud execution) where the cloud service (or something in between) returns 200 with a JSON body lacking the urls field: mis-scoped or invalid K6_CLOUD_TOKEN, an API version mismatch between k6 and the service, or a corporate proxy/mock rewriting the response body.

Common situations: Expired or wrongly-scoped cloud token; outdated k6 client against a changed cloud API; egress proxies or SSL-inspection middleboxes replacing the body with an error/HTML page that still parses as JSON; intermittent service behavior during incidents.

Related errors


AI-assisted analysis of grafana/k6@8d06114777 (2026-08-18). Data as JSON: /api/errors/a326423e69b0ab46. Report an issue: GitHub.

Appendix: source

Thrown at internal/js/modules/k6/browser/storage/file_persister.go:195

	bb, err := json.Marshal(b)
	if err != nil {
		return nil, fmt.Errorf("marshaling request body: %w", err)
	}

	return bb, nil
}

func readPresignedURLResponse(resp *http.Response) (PresignedURLResponse, error) {
	var rb PresignedURLResponse

	decoder := json.NewDecoder(resp.Body)
	err := decoder.Decode(&rb)
	if err != nil {
		return PresignedURLResponse{}, fmt.Errorf("decoding response body: %w", err)
	}

	if len(rb.URLs) == 0 {
		return PresignedURLResponse{}, errors.New("missing presigned url in response body")
	}

	return rb, nil
}

// newFileUploadRequest creates a new HTTP request to upload a file as a multipart
// form to the presigned URL received from the server.
func newFileUploadRequest(
	ctx context.Context,
	resp PresignedURLResponse,
	data io.Reader,
) (*http.Request, error) {
	// we don't support multiple presigned URLs at the moment.
	psu := resp.URLs[0]

	// copy all form fields received from a presigned URL
	// response to the multipart form fields.
	var form bytes.Buffer

View on GitHub (pinned to 8d06114777)