Automattic/mongoose · error · Error
Infinite subdocument loop: subdoc with _id ${parent._id} is
Error message
Infinite subdocument loop: subdoc with _id ${parent._id} is a parent of itself What it means
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.
Source
Thrown at lib/types/subdocument.js:278
return this.$__.ownerDocument;
}
let parent = this;
const paths = [];
const seenDocs = new Set([parent]);
while (true) {
if (typeof parent.$__pathRelativeToParent !== 'function') {
break;
}
paths.unshift(parent.$__pathRelativeToParent(void 0, true));
const _parent = parent.$parent();
if (_parent == null) {
break;
}
parent = _parent;
if (seenDocs.has(parent)) {
throw new Error('Infinite subdocument loop: subdoc with _id ' + parent._id + ' is a parent of itself');
}
seenDocs.add(parent);
}
this.$__.fullPath = paths.join('.');
this.$__.ownerDocument = parent;
return this.$__.ownerDocument;
};
/*!
* ignore
*/
Subdocument.prototype.$__fullPathWithIndexes = function() {
let parent = this;
const paths = [];View on GitHub (pinned to 49cdab0136)
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
Example fix
// before doc.tree.child = doc.tree; // cycle // after doc.tree.child = doc.tree.toObject(); // detached copy
Defensive patterns
Strategy: validation
Validate before calling
function isAncestorOf(ancestor, doc) {
let p = doc.$parent != null ? doc.$parent() : null;
while (p != null) { if (p === ancestor) return true; p = p.$parent != null ? p.$parent() : null; }
return false;
}
if (!isAncestorOf(value, parentDoc)) parentDoc.sub.child = value; Type guard
function isDetachedValue(v) { return v == null || v.$__ == null; } // plain objects are safe to nest Try / catch
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; } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Infinite subdocument loop: subdoc with _id ${doc._id} is a p
- Aggregate.prototype.exec() no longer accepts a callback
- Expected path "${path}" to be populated
- Cast to embedded failed for value "${value}" (type ${valueTy
- Cannot create use schema for property "${path}" because the
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/b0ba378d7cfd260c.
Report an issue: GitHub.