{"record":{"id":"0dc6710e605480a0","repo":"ruvnet/ruflo","slug":"canonical-json-does-not-support-cycles","errorCode":null,"errorMessage":"canonical JSON does not support cycles","messagePattern":"canonical JSON does not support cycles","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/codex/src/harness/repository-state.ts","lineNumber":131,"sourceCode":"    if (typeof item === 'string') {\n      assertUnicodeScalarString(item);\n      return JSON.stringify(item);\n    }\n    if (typeof item === 'number') {\n      if (\n        !Number.isFinite(item)\n        || Object.is(item, -0)\n        || (Number.isInteger(item) && !Number.isSafeInteger(item))\n      ) {\n        throw new Error('canonical JSON requires finite, safe, non-negative-zero numbers');\n      }\n      return JSON.stringify(item);\n    }\n    if (item === undefined) throw new Error('canonical JSON does not support undefined');\n    if (typeof item !== 'object') {\n      throw new Error(`canonical JSON does not support ${typeof item}`);\n    }\n    if (ancestors.has(item)) throw new Error('canonical JSON does not support cycles');\n    ancestors.add(item);\n    try {\n      if (Array.isArray(item)) return `[${item.map(encode).join(',')}]`;\n      const prototype = Object.getPrototypeOf(item);\n      if (prototype !== Object.prototype && prototype !== null) {\n        throw new Error('canonical JSON supports only arrays and plain objects');\n      }\n      const entries = Object.entries(item as Record<string, unknown>)\n        .sort(([left], [right]) => codeUnitCompare(left, right));\n      return `{${entries.map(([key, child]) => {\n        assertUnicodeScalarString(key);\n        return `${JSON.stringify(key)}:${encode(child)}`;\n      }).join(',')}}`;\n    } finally {\n      ancestors.delete(item);\n    }\n  };\n  return encode(value);","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/codex/src/harness/repository-state.ts#L113-L149","documentation":"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.","triggerScenarios":"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.","commonSituations":"Serializing ASTs, graphs, or domain models with parent pointers; objects enriched with a reference to their container; test fixtures wired with circular links.","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)"],"exampleFix":"// before\ncanonicalJson(taskGraph); // task.child.parent === task\n\n// after: project to acyclic DTOs linked by ids\ncanonicalJson(taskGraph.nodes.map(({ id, childId }) => ({ id, childId: childId ?? null })));","handlingStrategy":"validation","validationCode":"function assertAcyclic(value: unknown, seen = new Set<object>()): void {\n  if (Array.isArray(value)) {\n    if (seen.has(value)) throw new TypeError('cycle detected');\n    seen.add(value);\n    value.forEach((item) => assertAcyclic(item, seen));\n    seen.delete(value);\n  } else if (typeof value === 'object' && value !== null) {\n    if (seen.has(value)) throw new TypeError('cycle detected');\n    seen.add(value);\n    Object.values(value).forEach((item) => assertAcyclic(item, seen));\n    seen.delete(value);\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  return canonicalJson(payload);\n} catch (error) {\n  if (error instanceof Error && error.message === 'canonical JSON does not support cycles') {\n    throw new Error('payload contains a reference cycle; project it to an acyclic DTO (ids instead of links)');\n  }\n  throw error;\n}","preventionTips":["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"],"tags":["canonical-json","circular-reference","serialization"],"backgroundTag":"circular-reference","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}