{"record":{"id":"a326423e69b0ab46","repo":"grafana/k6","slug":"missing-presigned-url-in-response-body","errorCode":null,"errorMessage":"missing presigned url in response body","messagePattern":"missing presigned url in response body","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/browser/storage/file_persister.go","lineNumber":195,"sourceCode":"\tbb, err := json.Marshal(b)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"marshaling request body: %w\", err)\n\t}\n\n\treturn bb, nil\n}\n\nfunc readPresignedURLResponse(resp *http.Response) (PresignedURLResponse, error) {\n\tvar rb PresignedURLResponse\n\n\tdecoder := json.NewDecoder(resp.Body)\n\terr := decoder.Decode(&rb)\n\tif err != nil {\n\t\treturn PresignedURLResponse{}, fmt.Errorf(\"decoding response body: %w\", err)\n\t}\n\n\tif len(rb.URLs) == 0 {\n\t\treturn PresignedURLResponse{}, errors.New(\"missing presigned url in response body\")\n\t}\n\n\treturn rb, nil\n}\n\n// newFileUploadRequest creates a new HTTP request to upload a file as a multipart\n// form to the presigned URL received from the server.\nfunc newFileUploadRequest(\n\tctx context.Context,\n\tresp PresignedURLResponse,\n\tdata io.Reader,\n) (*http.Request, error) {\n\t// we don't support multiple presigned URLs at the moment.\n\tpsu := resp.URLs[0]\n\n\t// copy all form fields received from a presigned URL\n\t// response to the multipart form fields.\n\tvar form bytes.Buffer","sourceCodeStart":177,"sourceCodeEnd":213,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/browser/storage/file_persister.go#L177-L213","documentation":"The browser module's RemoteFilePersister uploads artifacts (screenshots, traces) to k6 Cloud. It first POSTs to a presigned-URL endpoint and expects a body with a non-empty URLs array; if the response decodes but the list is empty (file_persister.go:185-198), no presigned upload URL was granted and the artifact upload cannot proceed.","triggerScenarios":"Running k6 cloud (or cloud-exported browser tests) where the artifact service answers 200 OK but returns zero URLs: environment mismatch between the auth token and K6_CLOUD_HOST, a k6 build whose upload contract differs from the cloud API, or a project without browser-artifact storage entitlements.","commonSituations":"Pointing k6 at a different cloud environment than the token belongs to; self-hosted or air-gapped installations without S3 upload support; version skew between an old k6 binary and new cloud API (or vice versa); expired or mis-scoped API tokens.","solutions":["Verify K6_CLOUD_HOST (or --cloud-host) and the k6 Cloud token belong to the same environment","Upgrade k6 to the latest release so the presigned-URL request matches the current cloud API","Confirm the cloud project supports browser artifact storage and the token has upload permissions","If artifact upload is not needed for this run, disable it or run locally to unblock the test"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Preflight the cloud endpoint with the same token before the run\n// (outside k6): curl -H \"Authorization: Token $K6_CLOUD_TOKEN\" $K6_CLOUD_HOST/v1/check\n// Expect 200; otherwise fix host/token pairing before running browser cloud tests.","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep K6_CLOUD_HOST and the cloud token from the same environment","Update k6 regularly so presigned-URL requests match the current cloud API","Verify the project has browser-artifact storage entitlements before relying on cloud uploads"],"tags":["cloud","upload","browser","network","configuration"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}