ruvnet/ruflo · error · Error
canonical JSON does not support cycles
Error message
canonical JSON does not support cycles
What it means
canonicalJson tracks a set of ancestor objects while recursing; re-entering an object already on the stack means a reference cycle and throws immediately. Without this guard, deep recursion would overflow the stack, and a digest over a cyclic structure is not meaningful. The ancestor entry is removed after encoding, so DAGs (shared, non-circular references) are fine.
Source
Thrown at v3/@claude-flow/codex/src/harness/repository-state.ts:131
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 {
ancestors.delete(item);
}
};
return encode(value);View on GitHub (pinned to fa13ee4ad6)
Solutions
- Project the structure into a plain acyclic DTO tree before canonicalization (drop parent pointers)
- Run a sanitizer with a WeakSet seen-set that replaces repeated references with a stable marker or omits them
- Keep evidence payloads acyclic by construction (flat records, ID references instead of object links)
Example fix
// before
canonicalJson(taskGraph); // task.child.parent === task
// after: project to acyclic DTOs linked by ids
canonicalJson(taskGraph.nodes.map(({ id, childId }) => ({ id, childId: childId ?? null }))); Defensive patterns
Strategy: validation
Validate before calling
function assertAcyclic(value: unknown, seen = new Set<object>()): void {
if (Array.isArray(value)) {
if (seen.has(value)) throw new TypeError('cycle detected');
seen.add(value);
value.forEach((item) => assertAcyclic(item, seen));
seen.delete(value);
} else if (typeof value === 'object' && value !== null) {
if (seen.has(value)) throw new TypeError('cycle detected');
seen.add(value);
Object.values(value).forEach((item) => assertAcyclic(item, seen));
seen.delete(value);
}
} Try / catch
try {
return canonicalJson(payload);
} catch (error) {
if (error instanceof Error && error.message === 'canonical JSON does not support cycles') {
throw new Error('payload contains a reference cycle; project it to an acyclic DTO (ids instead of links)');
}
throw error;
} Prevention
- Project domain graphs to flat records linked by IDs before hashing
- Strip parent/back-references when building DTOs for evidence
- Unit-test serialization of any structure known to be bidirectional
When it happens
Trigger: Structures like node.parent = node, a.children = [a], bidirectional relations, memo caches that store their own wrapper, or ORM entities with back-references passed straight into evidence.
Common situations: Serializing ASTs, graphs, or domain models with parent pointers; objects enriched with a reference to their container; test fixtures wired with circular links.
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 undefined
- canonical JSON does not support ${typeof item}
- canonical JSON supports only arrays and plain objects
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/0dc6710e605480a0.
Report an issue: GitHub.