{"record":{"id":"2fda3f96838f9ca9","repo":"grafana/k6","slug":"format-of-value-must-be-k-v-received-q","errorCode":null,"errorMessage":"format of value must be k=v, received %q","messagePattern":"format of value must be k=v, received %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/browser/browser/file_persister.go","lineNumber":52,"sourceCode":"}\n\n// parsePresignedURLEnvVar will parse a value such as:\n// url=https://127.0.0.1/,basePath=/screenshots,header.1=a,header.2=b\n// and return them.\n//\n\nfunc parsePresignedURLEnvVar(envVarValue string) (presignedURLConfig, error) {\n\tss := strings.Split(envVarValue, \",\")\n\n\tpresignedURL := presignedURLConfig{\n\t\theaders: make(map[string]string),\n\t}\n\tfor _, s := range ss {\n\t\t// The key value pair should be of the form key=value, so split\n\t\t// on '=' to retrieve the key and value separately.\n\t\tkv := strings.Split(s, \"=\")\n\t\tif len(kv) != 2 {\n\t\t\treturn presignedURLConfig{}, fmt.Errorf(\"format of value must be k=v, received %q\", s)\n\t\t}\n\n\t\tk := kv[0]\n\t\tv := kv[1]\n\n\t\t// A key with \"header.\" means that the header name is encoded in the\n\t\t// key, separated by a \".\". Split the header on \".\" to retrieve the\n\t\t// header name. The header value should be present in v from the previous\n\t\t// split.\n\t\tvar hv, hk string\n\t\tif strings.Contains(k, \"header.\") {\n\t\t\thv = v\n\n\t\t\thh := strings.Split(k, \".\")\n\t\t\tif len(hh) != 2 {\n\t\t\t\treturn presignedURLConfig{}, fmt.Errorf(\"format of header must be header.k=v, received %q\", s)\n\t\t\t}\n","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/browser/browser/file_persister.go#L34-L70","documentation":"A single comma-separated segment of K6_BROWSER_SCREENSHOTS_OUTPUT does not contain exactly one '=' (file_persister.go:52). The parser splits each segment on '=' and requires exactly two parts, so segments with zero '=' or with more than one '=' are rejected.","triggerScenarios":"K6_BROWSER_SCREENSHOTS_OUTPUT contains a trailing comma ('url=https://h/,', empty last segment), a bare token without '=' ('strict'), or a URL whose query string itself contains '=' ('url=https://h/?X-Amz-Signature=abc' splits into three parts).","commonSituations":"Pasting a presigned S3/Azure URL with signature query parameters into the env var; YAML/CI env files that strip quotes; values copied from documentation examples where the '=' inside the value went unencoded.","solutions":["Ensure every comma-separated segment has exactly one '='; remove trailing/leading commas","URL-encode '=' inside values as %3D and ',' as %2C","If the presigned URL cannot be encoded cleanly, put the signed query into a header.* entry instead of the url value","Validate the value with a one-liner before launching k6: echo \"$V\" | tr ',' '\\n' | awk -F= 'NF!=2{print \"bad segment: \"$0; exit 1}'"],"exampleFix":"# before\nK6_BROWSER_SCREENSHOTS_OUTPUT=\"url=https://h/?sig=abc,basePath=/s\"\n# after\nK6_BROWSER_SCREENSHOTS_OUTPUT=\"url=https://h/?sig%3Dabc,basePath=/s\"","handlingStrategy":"validation","validationCode":"// Node/JS pre-check\nfunction segmentsOk(v) {\n  return v.split(',').every(s => (s.match(/=/g) || []).length === 1 && s.trim().length > 0);\n}\nif (!segmentsOk(process.env.K6_BROWSER_SCREENSHOTS_OUTPUT || '')) {\n  throw new Error('K6_BROWSER_SCREENSHOTS_OUTPUT: every segment must be exactly k=v');\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never paste presigned URLs with raw query strings into the env var - encode or move auth to headers","Avoid trailing commas and empty segments","Add the one-line awk/tr check to CI before k6 runs"],"tags":["k6","browser","environment-variable","parsing","screenshots"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}