{"record":{"id":"6fffd767cf0f3574","repo":"danny-avila/LibreChat","slug":"path-contains-symbol-keys","errorCode":null,"errorMessage":"${path} contains symbol keys","messagePattern":"(.+?) contains symbol keys","errorType":"validation","errorClass":"AgentRunEnvelopeError","httpStatus":null,"severity":"error","filePath":"packages/api/src/agents/envelope.ts","lineNumber":131,"sourceCode":"      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);\n        if (\n          !Number.isSafeInteger(index) ||\n          index < 0 ||\n          index >= value.length ||\n          String(index) !== key\n        ) {\n          throw new AgentRunEnvelopeError(`${path} contains non-index array properties`);\n        }","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/danny-avila/LibreChat/blob/5ff282f9006c436e561de1afd39a481bea1ef0d8/packages/api/src/agents/envelope.ts#L113-L149","documentation":"Thrown by cloneJsonValue when an object or array in the payload has one or more own symbol-keyed properties (Object.getOwnPropertySymbols returns a non-empty list). JSON only has string keys, so symbol-keyed properties are silently dropped by JSON.stringify; the envelope clone rejects them outright to make the loss explicit. This catches hidden 'tag' symbols that libraries attach to mark objects (e.g. React $$typeof, mongoose symbols).","triggerScenarios":"Passing a Mongoose document, a React element, a styled-components object, or any value that has been processed by a library that tags objects with Symbol() keys. Also a manually set obj[Symbol('flag')] = true.","commonSituations":"Passing a raw ORM/document object straight into the envelope instead of a plain DTO; spreading a library-annotated object into the payload; using a Symbol as a private field convention.","solutions":["Map the object to a plain literal with only the fields you need (pick/serialize) before placing it in the payload.","Call Object.getOwnPropertySymbols(obj).forEach(s => delete obj[s]) if you intentionally want to strip them (only when safe).","Construct payload objects from explicit field lists rather than spreading SDK/ORM return values."],"exampleFix":"// before\nconst doc = await Model.findById(id).lean(); // may carry mongoose symbols\ncreateAgentRunEnvelope({ protocol, requestId, receivedAt, principal, payload: { record: doc } });\n\n// after\nconst doc = await Model.findById(id).lean();\nconst record = JSON.parse(JSON.stringify(doc)); // or explicit pick\ncreateAgentRunEnvelope({ protocol, requestId, receivedAt, principal, payload: { record } });","handlingStrategy":"type-guard","validationCode":"function hasSymbolKeys(value: unknown): boolean {\n  return typeof value === 'object' && value !== null && Object.getOwnPropertySymbols(value).length > 0;\n}\nfunction stripSymbolsDeep(obj: unknown): unknown {\n  if (Array.isArray(obj)) return obj.map(stripSymbolsDeep);\n  if (obj && typeof obj === 'object') {\n    const clean: Record<string, unknown> = {};\n    for (const [k, v] of Object.entries(obj)) clean[k] = stripSymbolsDeep(v);\n    return clean;\n  }\n  return obj;\n}","typeGuard":"function isSymbolFree(value: unknown, seen = new WeakSet()): boolean {\n  if (typeof value !== 'object' || value === null) return true;\n  if (seen.has(value as object)) return true; // cycle handled elsewhere\n  seen.add(value as object);\n  if (Object.getOwnPropertySymbols(value).length > 0) return false;\n  const vals = Array.isArray(value) ? value : Object.values(value);\n  return vals.every((v) => isSymbolFree(v, seen));\n}","tryCatchPattern":"try {\n  const env = createAgentRunEnvelope(input);\n} catch (e) {\n  if (e instanceof AgentRunEnvelopeError && /symbol keys/.test(e.message)) {\n    input.payload = JSON.parse(JSON.stringify(input.payload)) as typeof input.payload;\n    // retry — JSON round-trip drops symbol keys\n  } else throw e;\n}","preventionTips":["Convert ORM/SDK objects to plain DTOs before placing them in the payload.","In tests, assert Object.getOwnPropertySymbols(payload).length === 0 for representative payloads.","Avoid the Symbol-as-private-field convention inside objects destined for the envelope."],"tags":["serialization","json","symbol","agent-envelope","payload-validation"],"backgroundTag":null,"analyzedSha":"5ff282f9006c436e561de1afd39a481bea1ef0d8","analyzedAt":"2026-08-12T21:38:08.145Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}