Automattic/mongoose · error · Error
Infinite subdocument loop: subdoc with _id ${doc._id} is a p
Error message
Infinite subdocument loop: subdoc with _id ${doc._id} is a parent of itself What it means
When modified subdocument paths are cleaned (cleanModifiedSubpaths, run during save()/removal bookkeeping), Mongoose walks up each subdocument's $parent() chain to clear modified paths on ancestors. A seen Set detects when the walk revisits the same subdocument - meaning the subdoc is its own ancestor, a cyclic subdocument hierarchy - and throws Error('Infinite subdocument loop: subdoc with _id ... is a parent of itself') instead of recursing forever.
Source
Thrown at lib/helpers/document/cleanModifiedSubpaths.js:37
if (schemaType?.$isMongooseDocumentArray) {
continue;
}
}
if (modifiedPath.startsWith(path + '.')) {
doc.$__.activePaths.clearPath(modifiedPath);
++deleted;
if (doc.$isSubdocument) {
cleanParent(doc, modifiedPath);
}
}
}
return deleted;
};
function cleanParent(doc, path, seen = new Set()) {
if (seen.has(doc)) {
throw new Error('Infinite subdocument loop: subdoc with _id ' + doc._id + ' is a parent of itself');
}
const parent = doc.$parent();
const newPath = doc.$__pathRelativeToParent(void 0, false) + '.' + path;
parent.$__.activePaths.clearPath(newPath);
if (parent.$isSubdocument) {
cleanParent(parent, newPath, seen);
}
}
View on GitHub (pinned to 49cdab0136)
Solutions
- Break the cycle: remove self-referencing assignments - a subdoc instance must have exactly one parent chain.
- Clone before nesting: assign subdoc.toObject() (or a fresh instance) instead of the live instance.
- Model recursive structures with references (ObjectId + populate) instead of embedded self-nesting.
Example fix
// before const cat = doc.categories[0]; cat.subcategories = doc.categories; // the array contains cat itself await doc.save(); // Error: subdoc is a parent of itself // after (clone the data instead of aliasing the instances) const cat = doc.categories[0]; cat.subcategories = doc.categories.map(c => c.toObject()); await doc.save();
Defensive patterns
Strategy: validation
Validate before calling
// reject cyclic subdocument graphs before saving
function isOwnAncestor(doc) {
const seen = new Set();
let p = doc.$parent();
while (p != null) {
if (p === doc) return true;
if (seen.has(p)) return true;
seen.add(p);
p = p.$isSubdocument ? p.$parent() : null;
}
return false;
}
if (doc.$isSubdocument && isOwnAncestor(doc)) {
throw new Error('cyclic subdocument hierarchy: subdoc is its own ancestor');
} Prevention
- Never assign a live subdocument instance into its own subtree; clone with toObject() first.
- Construct nested data from plain objects and let Mongoose instantiate subdocs exactly once.
- For recursive or unbounded structures use refs + populate instead of embedding.
When it happens
Trigger: Assigning a subdocument (or an array containing it) into its own subtree, e.g. doc.children[0].children = doc.children or sub.field = sub; reusing one subdocument instance at multiple depths of the same document; then triggering save()/pull/remove so cleanModifiedSubpaths and cleanParent run.
Common situations: Building trees in memory by linking existing subdoc instances; copy logic that assigns references instead of cloning; aliasing one subdoc instance into sibling branches of the same parent document.
Related errors
- Aggregate.prototype.exec() no longer accepts a callback
- Infinite subdocument loop: subdoc with _id ${parent._id} is
- Expected path "${path}" to be populated
- For your own good, using `document.save()` to update an arra
- a circular reference in the update value, updateValue: ${uti
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/1fd128d6abbc14d7.
Report an issue: GitHub.