grafana/k6 · error
unable to normalize number strings: %w
Error message
unable to normalize number strings: %w
What it means
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.
Source
Thrown at internal/js/modules/k6/grpc/client.go:388
if err != nil {
return grpcReq, fmt.Errorf("invalid GRPC's client.invoke() parameters: %w", err)
}
// k6 GRPC Invoke's default timeout is 2 minutes
if p.Timeout == time.Duration(0) {
p.Timeout = 2 * time.Minute
}
if req == nil {
return grpcReq, errors.New("request cannot be nil")
}
object := req.ToObject(c.vu.Runtime())
var stack []*sobek.Object
normalized, err := normalizeNumberStrings(object, c.vu.Runtime(), stack)
if err != nil {
return grpcReq, fmt.Errorf("unable to normalize number strings: %w", err)
}
b, err := normalized.ToObject(c.vu.Runtime()).MarshalJSON()
if err != nil {
return grpcReq, fmt.Errorf("unable to serialise request object: %w", err)
}
p.SetSystemTags(state, c.addr, method)
return grpcext.InvokeRequest{
Method: method,
MethodDescriptor: methodDesc,
Timeout: p.Timeout,
DiscardResponseMessage: p.DiscardResponseMessage,
Message: b,
TagsAndMeta: &p.TagsAndMeta,
Metadata: p.Metadata,
}, nilView on GitHub (pinned to 93accf6570)
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
Example fix
// before
const req = { id: 1 };
const container = { name: 'root', req };
req.parent = container; // cycle
client.invoke('/svc/Do', req);
// after
client.invoke('/svc/Do', { id: req.id }); Defensive patterns
Strategy: type-guard
Validate before calling
// optional early check: JSON.stringify throws on the same cycles
try { JSON.stringify(req); } catch (e) { throw new Error(`request is not serializable: ${e.message}`); } Type guard
function isAcyclic(value, seen = new WeakSet()) {
if (value === null || typeof value !== 'object') return true;
if (seen.has(value)) return false;
seen.add(value);
return Object.values(value).every(v => isAcyclic(v, seen));
}
if (!isAcyclic(req)) throw new Error('request contains a cyclic reference'); Try / catch
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; } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- cyclic reference to an object found
- method %q not found in file descriptors
- invalid GRPC's client.invoke() parameters: %w
- unable to serialise request object: %w
- no gRPC connection, you must call connect first
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/7fae7e6932237ca0.
Report an issue: GitHub.