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

  1. Break the cycle: remove self-referencing assignments - a subdoc instance must have exactly one parent chain.
  2. Clone before nesting: assign subdoc.toObject() (or a fresh instance) instead of the live instance.
  3. 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

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


AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21). Data as JSON: /api/errors/1fd128d6abbc14d7. Report an issue: GitHub.