ruvnet/ruflo · error · Error
canonical JSON does not support undefined
Error message
canonical JSON does not support undefined
What it means
canonicalJson refuses the value undefined at the top level or inside arrays, where it is observable (plain JSON.stringify would silently encode [undefined] as '[null]', hiding data loss). Note the asymmetry: inside plain objects, keys whose value is undefined are dropped silently by Object.entries and do not throw — only a directly encoded undefined does.
Source
Thrown at v3/@claude-flow/codex/src/harness/repository-state.ts:127
export function canonicalJson(value: unknown): string {
const ancestors = new Set<object>();
const encode = (item: unknown): string => {
if (item === null || typeof item === 'boolean') return JSON.stringify(item);
if (typeof item === 'string') {
assertUnicodeScalarString(item);
return JSON.stringify(item);
}
if (typeof item === 'number') {
if (
!Number.isFinite(item)
|| Object.is(item, -0)
|| (Number.isInteger(item) && !Number.isSafeInteger(item))
) {
throw new Error('canonical JSON requires finite, safe, non-negative-zero numbers');
}
return JSON.stringify(item);
}
if (item === undefined) throw new Error('canonical JSON does not support undefined');
if (typeof item !== 'object') {
throw new Error(`canonical JSON does not support ${typeof item}`);
}
if (ancestors.has(item)) throw new Error('canonical JSON does not support cycles');
ancestors.add(item);
try {
if (Array.isArray(item)) return `[${item.map(encode).join(',')}]`;
const prototype = Object.getPrototypeOf(item);
if (prototype !== Object.prototype && prototype !== null) {
throw new Error('canonical JSON supports only arrays and plain objects');
}
const entries = Object.entries(item as Record<string, unknown>)
.sort(([left], [right]) => codeUnitCompare(left, right));
return `{${entries.map(([key, child]) => {
assertUnicodeScalarString(key);
return `${JSON.stringify(key)}:${encode(child)}`;
}).join(',')}}`;
} finally {View on GitHub (pinned to fa13ee4ad6)
Solutions
- Use null to represent absent values explicitly in evidence payloads
- Filter undefined out of arrays before canonicalization: arr.filter((x) => x !== undefined)
- Narrow optional variables to a defined value before building the payload
Example fix
// before
const tags = items.map((i) => i.tag); // some undefined
const digest = canonicalJson({ tags });
// after
const tags = items.map((i) => i.tag ?? null);
const digest = canonicalJson({ tags }); Defensive patterns
Strategy: validation
Validate before calling
function stripUndefined(value: unknown): unknown {
if (Array.isArray(value)) return value.filter((item) => item !== undefined).map(stripUndefined);
if (typeof value === 'object' && value !== null) {
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, stripUndefined(v)]));
}
return value;
} Type guard
function isDefined<T>(value: T | undefined): value is T {
return value !== undefined;
} Try / catch
try {
return canonicalJson(payload);
} catch (error) {
if (error instanceof Error && error.message === 'canonical JSON does not support undefined') {
return canonicalJson(stripUndefined(payload));
}
throw error;
} Prevention
- Use null (not undefined) to mean 'absent' inside evidence payloads
- Filter undefined out of arrays built from conditional pushes or partial maps
- Narrow optional variables before constructing the payload
When it happens
Trigger: canonicalJson(undefined) when an optional variable was never assigned; arrays built with explicit undefined holes (arr.push(cond ? value : undefined)); map() callbacks that return undefined on some branches.
Common situations: Optional evidence fields flowing into arrays; destructuring with missing defaults; conditional spreads that leave undefined elements in position.
Related errors
- canonical JSON does not support lone UTF-16 surrogates
- canonical JSON requires finite, safe, non-negative-zero numb
- canonical JSON does not support ${typeof item}
- canonical JSON does not support cycles
- canonical JSON supports only arrays and plain objects
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/3240380cfc280121.
Report an issue: GitHub.