meteor/meteor · error · Error

Can't change a doc's _id while updating

Error message

Can't change a doc's _id while updating

What it means

Thrown by LocalCollection._updateInResultsSync when, during an ordered/unordered observe on a local cache, a doc produced by applying a change has an _id that is not EJSON.equals to the previous doc's _id. The observe machinery keys documents by _id; an _id change mid-update would corrupt the result-set index, so it is rejected. This is an internal invariant of the reactive cache, not a direct user-facing API.

Source

Thrown at packages/minimongo/local_collection.js:1758

};

// Is this selector just shorthand for lookup by _id?
LocalCollection._selectorIsId = selector =>
  typeof selector === 'number' ||
  typeof selector === 'string' ||
  selector instanceof MongoID.ObjectID
;

// Is the selector just lookup by _id (shorthand or not)?
LocalCollection._selectorIsIdPerhapsAsObject = selector =>
  LocalCollection._selectorIsId(selector) ||
  LocalCollection._selectorIsId(selector && selector._id) &&
  Object.keys(selector).length === 1
;

LocalCollection._updateInResultsSync = (query, doc, old_doc) => {
  if (!EJSON.equals(doc._id, old_doc._id)) {
    throw new Error('Can\'t change a doc\'s _id while updating');
  }

  const projectionFn = query.projectionFn;
  const changedFields = DiffSequence.makeChangedFields(
    projectionFn(doc),
    projectionFn(old_doc)
  );

  if (!query.ordered) {
    if (Object.keys(changedFields).length) {
      query.changed(doc._id, changedFields);
      query.results.set(doc._id, doc);
    }

    return;
  }

  const old_idx = LocalCollection._findInOrderedResults(query, doc);

View on GitHub (pinned to 5076d2f818)

Solutions

  1. Ensure transforms do not alter the _id (return a new object but copy _id verbatim).
  2. Make sure added and changed messages for the same logical doc use EJSON-equal _id values, including matching id type (string vs ObjectID).
  3. Remove _id from any 'changed' fields your publish function emits; _id must be immutable per document.
  4. If you are seeing this on a normal subscription, file a bug against the publish function — the client cache treats _id as immutable.
Defensive patterns

Strategy: type-guard

Type guard

// For transforms: ensure _id is preserved verbatim.
function safeTransform(fn) {
  return (doc) => {
    const out = fn(doc);
    if (!EJSON.equals(out._id, doc._id)) {
      throw new TypeError('Transform must not change _id');
    }
    return out;
  };
}
cursor.observe({ /* ... */ }, { transform: safeTransform(myTransform) });

Prevention

When it happens

Trigger: A transform function passed to observe mutates the _id; a server 'changed' message includes an _id field that differs from the added doc's _id; a projection that rewrites _id; two different server docs sharing an id collision after a transform. Also reproducible by manually poking the cache or by a buggy publish that sends changed{_id: newId} for an existing doc.

Common situations: Custom transform on a cursor that returns a new object whose _id is computed/normalized differently; EJSON id type mismatch (string vs ObjectID vs MongoID.ObjectID) where EJSON.equals returns false even though the values look identical as strings; a publication that mistakenly sends _id in changed fields.

Related errors


AI-assisted analysis of meteor/meteor@5076d2f818 (2026-08-13). Data as JSON: /api/errors/46696087748915a7. Report an issue: GitHub.