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

  1. Clone before nesting: parent.sub.child = originalDoc.toObject() or new Subdoc(originalDoc.toObject())
  2. Never insert a live subdocument instance beneath itself — create a fresh plain object or new model instance
  3. 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

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


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