{"record":{"id":"a24f7c1b24cd63b9","repo":"danny-avila/LibreChat","slug":"path-contains-a-circular-reference","errorCode":null,"errorMessage":"${path} contains a circular reference","messagePattern":"(.+?) contains a circular reference","errorType":"validation","errorClass":"AgentRunEnvelopeError","httpStatus":null,"severity":"error","filePath":"packages/api/src/agents/envelope.ts","lineNumber":123,"sourceCode":"  }\n\n  if (value === null || typeof value === 'string' || typeof value === 'boolean') {\n    return value;\n  }\n\n  if (typeof value === 'number') {\n    if (!Number.isFinite(value)) {\n      throw new AgentRunEnvelopeError(`${path} must contain only finite numbers`);\n    }\n    return value;\n  }\n\n  if (typeof value !== 'object') {\n    throw new AgentRunEnvelopeError(`${path} contains a non-JSON ${typeof value} value`);\n  }\n\n  if (ancestors.has(value)) {\n    throw new AgentRunEnvelopeError(`${path} contains a circular reference`);\n  }\n\n  ancestors.add(value);\n\n  try {\n    const symbolKeys = Object.getOwnPropertySymbols(value);\n    if (symbolKeys.length > 0) {\n      throw new AgentRunEnvelopeError(`${path} contains symbol keys`);\n    }\n\n    if (Array.isArray(value)) {\n      const cloned: unknown[] = new Array(value.length);\n      let clonedItemCount = 0;\n      for (const key of Object.getOwnPropertyNames(value)) {\n        if (key === 'length') {\n          continue;\n        }\n        const index = Number(key);","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/danny-avila/LibreChat/blob/5ff282f9006c436e561de1afd39a481bea1ef0d8/packages/api/src/agents/envelope.ts#L105-L141","documentation":"Thrown by cloneJsonValue when an object inside the payload is revisited while still on its own ancestor chain (tracked via a WeakSet 'ancestors'). A self-referential or mutually-referential object graph cannot be serialized to JSON and would recurse forever, so the clone refuses it. The WeakSet is scoped per top-level clone and entries are deleted on the way back up, so a value appearing in two sibling branches is fine; only a true cycle trips this.","triggerScenarios":"A payload object assigned to itself (a.self = a), two objects referencing each other (a.b = b; b.a = a), or a tree node whose parent pointer re-enters the graph.","commonSituations":"Reusing a memoized request object that was mutated to point back at a parent; passing a DOM-like or AST node tree with parent links into the agent payload; building a graph cache and accidentally embedding it.","solutions":["Follow the path in the message to the first object that closes the cycle.","Sever the back-reference (delete the parent pointer) or replace the cyclic structure with an acyclic, JSON-friendly shape (e.g. ids instead of object links).","Run JSON.stringify(payload) locally before calling createAgentRunEnvelope; if it throws 'circular', fix the shape there first."],"exampleFix":"// before\nconst node = { id: 1, label: 'root' };\nnode.parent = node;\ncreateAgentRunEnvelope({ protocol, requestId, receivedAt, principal, payload: { tree: node } });\n\n// after\nconst node = { id: 1, label: 'root', parentId: null };\ncreateAgentRunEnvelope({ protocol, requestId, receivedAt, principal, payload: { tree: node } });","handlingStrategy":"validation","validationCode":"function isAcyclic(value: unknown, seen = new WeakSet()): boolean {\n  if (typeof value !== 'object' || value === null) return true;\n  if (seen.has(value as object)) return false;\n  seen.add(value as object);\n  if (Array.isArray(value)) return (value as unknown[]).every((v) => isAcyclic(v, seen));\n  return Object.values(value).every((v) => isAcyclic(v, seen));\n}\nif (!isAcyclic(payload)) throw new Error('payload contains a cycle');","typeGuard":"function isAcyclicJson(value: unknown, seen = new WeakSet()): boolean {\n  if (value === null || typeof value !== 'object') return true;\n  if (seen.has(value as object)) return false;\n  seen.add(value as object);\n  const vals = Array.isArray(value) ? value : Object.values(value);\n  return vals.every((v) => isAcyclicJson(v, seen));\n}","tryCatchPattern":"try {\n  const env = createAgentRunEnvelope(input);\n} catch (e) {\n  if (e instanceof AgentRunEnvelopeError && /circular reference/.test(e.message)) {\n    // the path names the cycle origin; sever that back-reference and retry\n  } else throw e;\n}","preventionTips":["Prefer id references over object back-pointers in payload shapes.","Validate payloads with JSON.stringify in unit tests; a thrown 'Converting circular structure' fails the test before runtime.","Avoid reusing long-lived memoized request objects that may be mutated to form cycles."],"tags":["serialization","circular-reference","json","agent-envelope","payload-validation"],"backgroundTag":null,"analyzedSha":"5ff282f9006c436e561de1afd39a481bea1ef0d8","analyzedAt":"2026-08-12T21:38:08.145Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}