{"record":{"id":"b0ba378d7cfd260c","repo":"Automattic/mongoose","slug":"infinite-subdocument-loop-subdoc-with-id-paren","errorCode":null,"errorMessage":"Infinite subdocument loop: subdoc with _id ${parent._id} is a parent of itself","messagePattern":"Infinite subdocument loop: subdoc with _id (.+?) is a parent of itself","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/types/subdocument.js","lineNumber":278,"sourceCode":"    return this.$__.ownerDocument;\n  }\n\n  let parent = this;\n  const paths = [];\n  const seenDocs = new Set([parent]);\n\n  while (true) {\n    if (typeof parent.$__pathRelativeToParent !== 'function') {\n      break;\n    }\n    paths.unshift(parent.$__pathRelativeToParent(void 0, true));\n    const _parent = parent.$parent();\n    if (_parent == null) {\n      break;\n    }\n    parent = _parent;\n    if (seenDocs.has(parent)) {\n      throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself');\n    }\n\n    seenDocs.add(parent);\n  }\n\n  this.$__.fullPath = paths.join('.');\n\n  this.$__.ownerDocument = parent;\n  return this.$__.ownerDocument;\n};\n\n/*!\n * ignore\n */\n\nSubdocument.prototype.$__fullPathWithIndexes = function() {\n  let parent = this;\n  const paths = [];","sourceCodeStart":260,"sourceCodeEnd":296,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/types/subdocument.js#L260-L296","documentation":"While computing a subdocument's full path, Mongoose walks the $parent() chain and records every ancestor in a seen-set. If the same document appears twice in the chain, the parent linkage forms a cycle and this error is thrown to prevent an infinite loop. It means a document was nested inside its own subtree.","triggerScenarios":"parent.sub.child = parent (direct self-nesting); assigning a live subdocument instance from one branch into a deeper branch of the same tree, e.g. doc.a.b = doc.a; reusing a subdoc instance as its own descendant, then triggering fullPath()/ownerDocument() via validation or save.","commonSituations":"Building self-referencing tree structures (categories, org charts) by moving existing subdoc instances instead of plain objects; memoizing subdocs and inserting them into their own descendants.","solutions":["Clone before nesting: parent.sub.child = originalDoc.toObject() or new Subdoc(originalDoc.toObject())","Never insert a live subdocument instance beneath itself — create a fresh plain object or new model instance","For trees, model children as a separate collection with ref/ObjectId parents instead of deep nesting"],"exampleFix":"// before\ndoc.tree.child = doc.tree; // cycle\n// after\ndoc.tree.child = doc.tree.toObject(); // detached copy","handlingStrategy":"validation","validationCode":"function isAncestorOf(ancestor, doc) {\n  let p = doc.$parent != null ? doc.$parent() : null;\n  while (p != null) { if (p === ancestor) return true; p = p.$parent != null ? p.$parent() : null; }\n  return false;\n}\nif (!isAncestorOf(value, parentDoc)) parentDoc.sub.child = value;","typeGuard":"function isDetachedValue(v) { return v == null || v.$__ == null; } // plain objects are safe to nest","tryCatchPattern":"try { await doc.save(); } catch (err) { if (/Infinite subdocument loop/.test(err.message)) throw new Error('Document tree contains a cycle — nest plain object copies instead of live subdocs'); throw err; }","preventionTips":["Nest plain objects (toObject()/literals), never live subdocument instances, when moving nodes within a tree","Model self-referential hierarchies as separate collections with ObjectId refs","Add a cycle check helper when accepting user-defined tree mutations"],"tags":["mongoose","subdocument","cycle","infinite-loop"],"backgroundTag":"cyclic-document-reference","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}