{"record":{"id":"7fae7e6932237ca0","repo":"grafana/k6","slug":"unable-to-normalize-number-strings-w","errorCode":null,"errorMessage":"unable to normalize number strings: %w","messagePattern":"unable to normalize number strings: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/grpc/client.go","lineNumber":388,"sourceCode":"\tif err != nil {\n\t\treturn grpcReq, fmt.Errorf(\"invalid GRPC's client.invoke() parameters: %w\", err)\n\t}\n\n\t// k6 GRPC Invoke's default timeout is 2 minutes\n\tif p.Timeout == time.Duration(0) {\n\t\tp.Timeout = 2 * time.Minute\n\t}\n\n\tif req == nil {\n\t\treturn grpcReq, errors.New(\"request cannot be nil\")\n\t}\n\n\tobject := req.ToObject(c.vu.Runtime())\n\n\tvar stack []*sobek.Object\n\tnormalized, err := normalizeNumberStrings(object, c.vu.Runtime(), stack)\n\tif err != nil {\n\t\treturn grpcReq, fmt.Errorf(\"unable to normalize number strings: %w\", err)\n\t}\n\n\tb, err := normalized.ToObject(c.vu.Runtime()).MarshalJSON()\n\tif err != nil {\n\t\treturn grpcReq, fmt.Errorf(\"unable to serialise request object: %w\", err)\n\t}\n\n\tp.SetSystemTags(state, c.addr, method)\n\n\treturn grpcext.InvokeRequest{\n\t\tMethod:                 method,\n\t\tMethodDescriptor:       methodDesc,\n\t\tTimeout:                p.Timeout,\n\t\tDiscardResponseMessage: p.DiscardResponseMessage,\n\t\tMessage:                b,\n\t\tTagsAndMeta:            &p.TagsAndMeta,\n\t\tMetadata:               p.Metadata,\n\t}, nil","sourceCodeStart":370,"sourceCodeEnd":406,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/grpc/client.go#L370-L406","documentation":"Before serializing a request, k6 deep-walks it with normalizeNumberStrings (converting NaN and +/-Infinity to strings so JSON encoding works); the walk aborts with 'cyclic reference to an object found' when any object participates in a reference cycle, surfaced as 'unable to normalize number strings: ...' (internal/js/modules/k6/grpc/client.go:388). Even one cycle in a deeply nested field triggers it.","triggerScenarios":"client.invoke(m, req) where req or any nested value references itself: req.self = req, parent/child objects pointing at each other, or a cyclic module-level shared structure passed as the message.","commonSituations":"Building request trees with back-references (audit entries with parent pointers); passing memoized/cached object graphs as protobuf messages; ORM-ish objects with owner links.","solutions":["Break the cycle: build a fresh plain object containing only the message fields","Use JSON.parse(JSON.stringify(req)) as a diagnostic - it throws on the same cycle and points at the offending path","Keep shared or cyclic state out of the actual message payload"],"exampleFix":"// before\nconst req = { id: 1 };\nconst container = { name: 'root', req };\nreq.parent = container; // cycle\nclient.invoke('/svc/Do', req);\n\n// after\nclient.invoke('/svc/Do', { id: req.id });","handlingStrategy":"type-guard","validationCode":"// optional early check: JSON.stringify throws on the same cycles\ntry { JSON.stringify(req); } catch (e) { throw new Error(`request is not serializable: ${e.message}`); }","typeGuard":"function isAcyclic(value, seen = new WeakSet()) {\n  if (value === null || typeof value !== 'object') return true;\n  if (seen.has(value)) return false;\n  seen.add(value);\n  return Object.values(value).every(v => isAcyclic(v, seen));\n}\nif (!isAcyclic(req)) throw new Error('request contains a cyclic reference');","tryCatchPattern":"try { client.invoke(m, req); } catch (e) { if (/cyclic reference|unable to normalize number strings/.test(e.message)) { /* rebuild req as a fresh plain object without back-references */ } throw e; }","preventionTips":["Construct request payloads as fresh plain objects per call","Never pass object graphs with parent/owner back-references as messages","Use JSON round-trips when bridging cached structures into requests"],"tags":["grpc","k6","cyclic-reference","json","invoke"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}