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
- Verify the cloud credentials: K6_CLOUD_TOKEN (or k6 login) valid and scoped for the project
- Upgrade k6 to the latest version so the persister matches the current cloud API contract
- Capture the actual response body (proxy/debug logs, HTTPS_PROXY inspection) to see what the service really returned
- If cloud output is not intended, remove --out cloud / cloud env settings so no persister call happens
- 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
- Keep K6_CLOUD_TOKEN fresh and correctly scoped
- Stay on a current k6 release so the persister matches the cloud API
- Watch for proxies/SSL inspection altering cloud response bodies
- If cloud output is not needed, remove --out cloud entirely
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
- archive upload failed
- archive upload failed
- creating upload request
- decoding response
- format of value must be k=v, received
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.BufferView on GitHub (pinned to 8d06114777)