mongodb/node-mongodb-native · error · MongoChangeStreamError

A change stream document has been received that lacks a resu

Error message

A change stream document has been received that lacks a resume token (_id).

What it means

Thrown as a MongoChangeStreamError when a change document is received from the server but does not contain an _id field. The _id is the resume token that the driver needs to cache so it can resume the stream after a disconnect. Without it, the driver cannot guarantee at-least-once delivery and rejects the document.

Source

Thrown at src/change_stream.ts:997

    this.cursorStream?.destroy();
    this.cursorStream = undefined;
  }

  /** @internal */
  private _processChange(change: TChange | null): TChange {
    if (this.isClosed) {
      // TODO(NODE-3485): Replace with MongoChangeStreamClosedError
      throw new MongoAPIError(CHANGESTREAM_CLOSED_ERROR);
    }

    // a null change means the cursor has been notified, implicitly closing the change stream
    if (change == null) {
      // TODO(NODE-3485): Replace with MongoChangeStreamClosedError
      throw new MongoRuntimeError(CHANGESTREAM_CLOSED_ERROR);
    }

    if (change && !change._id) {
      throw new MongoChangeStreamError(NO_RESUME_TOKEN_ERROR);
    }

    // cache the resume token
    this.cursor.cacheResumeToken(change._id);

    // wipe the startAtOperationTime if there was one so that there won't be a conflict
    // between resumeToken and startAtOperationTime if we need to reconnect the cursor
    this.options.startAtOperationTime = undefined;

    return change;
  }

  /** @internal */
  private _processErrorStreamMode(changeStreamError: AnyError, cursorInitialized: boolean) {
    // If the change stream has been closed explicitly, do not process error.
    if (this.isClosed) return;

    if (

View on GitHub (pinned to 3366c21a63)

Solutions

  1. Inspect the pipeline passed to watch(); ensure no stage removes or renames the change document's _id field.
  2. If you must transform documents, do so in your application after receiving the change, not in the $changeStream pipeline.
  3. Upgrade mongod to a patched version if you have confirmed the pipeline is correct and the server is omitting _id.

Example fix

// before
const cs = collection.watch([
  { $project: { _id: 0, fullDocument: 1 } } // strips resume token
]);

// after
const cs = collection.watch([]);
cs.on('change', change => {
  const { fullDocument } = change; // transform in app code
});
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the watch pipeline does not strip _id
function assertPipelinePreservesResumeToken(pipeline) {
  for (const stage of pipeline) {
    if (stage.$project && stage.$project._id === 0) throw new Error('pipeline removes resume token _id');
    if (stage.$unset && (stage.$unset.includes('_id'))) throw new Error('pipeline removes resume token _id');
  }
}

Type guard

// Not applicable: validates aggregation stage shape rather than a TS type.

Try / catch

cs.on('error', err => {
  if (err instanceof MongoChangeStreamError && /lacks a resume token/.test(err.message)) {
    // audit and fix the pipeline, then reopen
  } else throw err;
});

Prevention

When it happens

Trigger: A change document lacking _id reaches _processChange(); typically caused by a malformed aggregation pipeline that strips _id (e.g. a $project stage that excludes it), or a non-standard upstream stage in the pipeline passed to collection.watch().

Common situations: Adding a $project/$unset stage to the watch pipeline that removes the _id field; using $replaceRoot with a document that drops _id; server bugs in older versions emitting tokenless changes.

Related errors


AI-assisted analysis of mongodb/node-mongodb-native@3366c21a63 (2026-08-04). Data as JSON: /data/errors/87d654e55906092c.json. Report an issue: GitHub.