ruvnet/ruflo · error · TypeError
Federation canonicalization rejects cyclic values
Error message
Federation canonicalization rejects cyclic values
What it means
Cycle detection during JCS canonicalization: an ancestors set of in-progress objects catches circular references before recursion, throwing instead of overflowing the stack. JSON has no reference syntax, so a cyclic payload cannot be represented canonically and must be rejected — both sides could never agree on serialized bytes.
Source
Thrown at v3/@claude-flow/plugin-agent-federation/src/application/inbound-dispatcher.ts:171
}
if (Number.isInteger(value) && !Number.isSafeInteger(value)) {
throw new TypeError('Federation canonicalization rejects unsafe integers');
}
return JSON.stringify(value);
case 'bigint':
case 'function':
case 'symbol':
case 'undefined':
throw new TypeError(`Federation canonicalization rejects ${typeof value}`);
case 'object':
break;
default:
throw new TypeError(`Federation canonicalization rejects ${typeof value}`);
}
const object = value as object;
if (ancestors.has(object)) {
throw new TypeError('Federation canonicalization rejects cyclic values');
}
ancestors.add(object);
try {
if (Array.isArray(value)) {
const items: string[] = [];
for (let index = 0; index < value.length; index += 1) {
if (!Object.prototype.hasOwnProperty.call(value, index)) {
throw new TypeError('Federation canonicalization rejects sparse arrays');
}
items.push(canonicalizeJcsValue(value[index], ancestors));
}
return `[${items.join(',')}]`;
}
const prototype = Object.getPrototypeOf(value);
if (prototype !== Object.prototype && prototype !== null) {
throw new TypeError('Federation canonicalization accepts only plain objects');View on GitHub (pinned to fa13ee4ad6)
Solutions
- Restructure the payload as a tree: replace back-references with an id the receiver resolves
- Serialize explicitly with a mapper that emits plain data (no parent pointers)
- Run a JSON.stringify pre-flight — it throws on cycles before you reach the canonicalizer, with a clearer origin
Example fix
// before msg.meta.parent.child = msg.meta; // cycle // after msg.meta.parent.childId = msg.meta.id; // reference by id
Defensive patterns
Strategy: validation
Validate before calling
// cheap pre-flight: JSON.stringify throws on cycles before canonicalization
try {
JSON.stringify(envelope.metadata);
} catch (e) {
throw new Error('refusing to sign cyclic metadata', { cause: e });
} Type guard
function isAcyclic(value: unknown, ancestors: WeakSet<object> = new WeakSet()): boolean {
if (typeof value !== 'object' || value === null) return true;
if (ancestors.has(value)) return false;
ancestors.add(value);
try {
return Object.values(value).every(v => isAcyclic(v, ancestors));
} finally {
ancestors.delete(value);
}
} Prevention
- Model relationships by id reference, not object back-pointers
- Run JSON.stringify as a pre-sign sanity check
- Build envelopes via explicit mappers that emit plain trees
- Keep parent-pointer convenience structures out of payloads
When it happens
Trigger: Message metadata with parent-child cycles (a.child = b; b.parent = a); self-referential objects (obj.self = obj); shared back-references used to express DAGs inside signed payloads.
Common situations: Tree structures wired with parent pointers for convenience; ORM/graph model objects placed directly into envelopes; caches attaching bookkeeping back-links to cached nodes.
Related errors
- Federation canonicalization rejects non-canonical numbers
- Federation canonicalization rejects unsafe integers
- Federation canonicalization rejects ${typeof value}
- Federation canonicalization rejects sparse arrays
- Federation canonicalization accepts only plain objects
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/5f91553a3ff7840c.
Report an issue: GitHub.