{"record":{"id":"867124b1dedeb747","repo":"github/copilot-sdk","slug":"cyclic-value","errorCode":"cyclic_value","errorMessage":"${context.label} contains a cyclic reference at ${path}","messagePattern":"(.+?) contains a cyclic reference at (.+?)","errorType":"validation","errorClass":"ResponseError","httpStatus":null,"severity":"error","filePath":"nodejs/src/session.ts","lineNumber":2351,"sourceCode":"            typeof current === \"bigint\"\n        ) {\n            throw strictJsonValidationError(\n                context,\n                \"unsupported_type\",\n                `${context.label} contains a function, symbol, or BigInt at ${path}`,\n                path\n            );\n        }\n        if (typeof current !== \"object\") {\n            throw strictJsonValidationError(\n                context,\n                \"unsupported_type\",\n                `${context.label} contains a function, symbol, or BigInt at ${path}`,\n                path\n            );\n        }\n        if (ancestors.has(current)) {\n            throw strictJsonValidationError(\n                context,\n                \"cyclic_value\",\n                `${context.label} contains a cyclic reference at ${path}`,\n                path\n            );\n        }\n\n        ancestors.add(current);\n        try {\n            if (Array.isArray(current)) {\n                const keys = Reflect.ownKeys(current);\n                if (\n                    keys.length !== current.length + 1 ||\n                    keys.some(\n                        (key) =>\n                            key !== \"length\" &&\n                            (typeof key !== \"string\" ||\n                                !/^(0|[1-9]\\d*)$/.test(key) ||","sourceCodeStart":2333,"sourceCodeEnd":2369,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/nodejs/src/session.ts#L2333-L2369","documentation":"The strict JSON validator detects cyclic references while walking journaled factory values, using an ancestors set. JSON.stringify throws on cycles ('Converting circular structure to JSON'), so the library rejects them eagerly with the precise path instead of letting serialization fail later and corrupt the journal.","triggerScenarios":"Returning a factory result or step value that references itself directly (obj.self = obj) or indirectly (a.parent = a inside arrays/nested objects), e.g. attaching a parent back-reference for navigation.","commonSituations":"Doubly-linked tree nodes with parent pointers, DOM-like structures, caching an object inside itself, circular imports producing self-referencing module state.","solutions":["Follow the error's `path` and break the cycle — remove the back-reference or store an id/weak reference instead.","Serialize with a replacer that drops parent links, or map the structure to a plain JSON shape first.","For needed relationships, journal ids and rehydrate the graph after replay instead of journaling object references."],"exampleFix":"// before\nnode.parent = parent; // cycle when journaling node\nreturn { node };\n// after\nreturn { node: { ...node, parent: parent ? parent.id : null } };","handlingStrategy":"validation","validationCode":"function assertAcyclic(value, seen = new Set(), path = \"$\") {\n  if (value && typeof value === \"object\") {\n    if (seen.has(value)) throw new Error(`Cycle at ${path}`);\n    seen.add(value);\n    if (Array.isArray(value)) value.forEach((v, i) => assertAcyclic(v, seen, `${path}[${i}]`));\n    else for (const [k, v] of Object.entries(value)) assertAcyclic(v, seen, `${path}.${k}`);\n    seen.delete(value);\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  session.runFactory(step);\n} catch (e) {\n  if (e?.details?.category === \"cyclic_value\") {\n    console.error(`Break cyclic reference at ${e.details.path}`);\n  } else throw e;\n}","preventionTips":["Represent parent/child relations with ids, not object back-references.","Snapshot to plain JSON (JSON.parse(JSON.stringify(...)) with a replacer) before journaling complex graphs.","Never cache an object inside itself (memo.self = memo)."],"tags":["json","validation","cycles","factory-result"],"backgroundTag":"json-serialization-failed","analyzedSha":"cd8cf15dc3f9e762615790aaed0a771a0f392755","analyzedAt":"2026-09-09T18:32:31.973Z","contentChangedAt":"2026-09-09T18:32:31.973Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}