{"record":{"id":"43cc1b5bb4e4dd9e","repo":"Automattic/mongoose","slug":"a-circular-reference-in-the-update-value-updateva","errorCode":null,"errorMessage":"a circular reference in the update value, updateValue:\n${util.inspect(recursion.raw.update, { showHidden: false, depth: 1 })}\nupdatePath: '${recursion.raw.path}'","messagePattern":"a circular reference in the update value, updateValue:\n(.+?)\\)\\}\nupdatePath: '(.+?)'","errorType":"exception","errorClass":"MongooseError","httpStatus":null,"severity":"error","filePath":"lib/helpers/common.js","lineNumber":85,"sourceCode":"\n/*!\n * ignore\n */\n\nfunction modifiedPaths(update, path, result, recursion = null) {\n  if (update == null || typeof update !== 'object') {\n    return;\n  }\n\n  if (recursion == null) {\n    recursion = {\n      raw: { update, path },\n      trace: new WeakSet()\n    };\n  }\n\n  if (recursion.trace.has(update)) {\n    throw new MongooseError(`a circular reference in the update value, updateValue:\n${util.inspect(recursion.raw.update, { showHidden: false, depth: 1 })}\nupdatePath: '${recursion.raw.path}'`);\n  }\n  recursion.trace.add(update);\n\n  const keys = Object.keys(update || {});\n  const numKeys = keys.length;\n  result = result || {};\n  path = path ? path + '.' : '';\n\n  for (let i = 0; i < numKeys; ++i) {\n    const key = keys[i];\n    let val = update[key];\n\n    const _path = path + key;\n    result[_path] = true;\n    if (!Buffer.isBuffer(val) && isMongooseObject(val)) {\n      val = val.toObject({ transform: false, virtuals: false });","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/helpers/common.js#L67-L103","documentation":"Before applying an update, Mongoose flattens nested update objects (lib/helpers/common.js) for casting and diffing. It tracks visited objects in a WeakSet; encountering the same object again means the update value contains a circular reference, which could never be serialized to BSON, so it throws MongooseError with a util.inspect snapshot (depth 1) of the top-level update and the path where the cycle was detected.","triggerScenarios":"Model.updateOne(filter, { $set: obj }) where obj contains itself (obj.self = obj); update payloads built from entity or graph structures carrying parent back-references; nested documents whose child points back at an ancestor object.","commonSituations":"Passing ORM-ish or in-memory graph objects straight into updates; building hierarchical data (menus, trees) in place; accidentally assigning child.parent = parentNode and then embedding parentNode in the update.","solutions":["Build a plain, acyclic DTO for the update: map only the fields you want to persist.","Store references instead of embeddings: replace cycles with ObjectId refs, e.g. $set: { parent: parentNode._id }.","Detect and strip cycles with a walker before calling update APIs."],"exampleFix":"// before\nconst node = { name: 'root' };\nnode.self = node;\nawait Tree.updateOne({ _id: id }, { $set: node }); // throws: circular reference\n\n// after\nawait Tree.updateOne({ _id: id }, { $set: { name: 'root' } });\n// or store a reference instead of embedding:\nawait Tree.updateOne({ _id: childId }, { $set: { parent: rootNode._id } });","handlingStrategy":"validation","validationCode":"function hasCycle(value) {\n  const seen = new WeakSet();\n  function walk(v) {\n    if (v == null || typeof v !== 'object') return false;\n    if (seen.has(v)) return true;\n    seen.add(v);\n    return Object.values(v).some(walk);\n  }\n  return walk(value);\n}\nif (hasCycle(update)) {\n  throw new Error('update payload contains a circular reference');\n}\nawait Model.updateOne(filter, update);","typeGuard":"function isAcyclicPayload(update) {\n  return !hasCycle(update); // hasCycle defined in validationCode\n}","tryCatchPattern":"try {\n  await Model.updateOne(filter, update);\n} catch (err) {\n  if (err instanceof mongoose.MongooseError && err.message.includes('circular reference')) {\n    // rebuild the update as a plain DTO (pick explicit fields) and retry once\n  } else {\n    throw err;\n  }\n}","preventionTips":["Never pass live object graphs to update APIs; construct literal update objects at the call site.","Keep parent/child back-references as ObjectIds, not embedded objects.","Sanitize external payloads with schema validators (zod/joi) that rebuild plain objects."],"tags":["mongoose","update","circular-reference","serialization","bson"],"backgroundTag":"circular-reference","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}