mongodb/node-mongodb-native · error · MongoChangeStreamError

A change stream document has been received that lacks a…

Error message

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

What it means

Thrown by _processChange() when a change document from the server does not contain an _id field (the resume token). Every change stream notification must include a resume token so the driver can resume after interruptions. Receiving a document without one is a protocol violation that makes resumption impossible. This is a MongoChangeStreamError.

Solutions

  1. Review your change stream pipeline stages and ensure none remove or rename the _id field from change documents
  2. If you need to transform documents, do so in application code after receiving the change, not in the pipeline
  3. Add _id: 1 to any $project stage in the change stream pipeline

Example fix

// before
const stream = collection.watch([
  { $project: { 'fullDocument._id': 1, operationType: 1 } } // drops top-level _id
]);

// after
const stream = collection.watch([
  { $project: { _id: 1, 'fullDocument._id': 1, operationType: 1 } }
]);
Defensive patterns

Strategy: validation

Validate before calling

// Validate pipeline does not strip _id before starting the change stream
const FORBIDDEN_STAGES = ['$replaceRoot', '$replaceWith'];
const hasProjectWithoutId = pipeline.some(
  stage => stage.$project && stage.$project._id === 0
);
if (hasProjectWithoutId) {
  throw new Error('Change stream pipeline must not remove the _id resume token field');
}

Try / catch

try {
  for await (const change of changeStream) {
    processChange(change);
  }
} catch (error) {
  if (error instanceof MongoChangeStreamError && error.message.includes('resume token')) {
    // Pipeline is stripping _id; fix the pipeline and restart
    console.error('Fix pipeline to preserve _id field on change documents');
  }
}

Prevention

When it happens

Trigger: A custom aggregation pipeline stage (e.g., $project, $unset, $replaceRoot) that removes or renames the _id field from change documents; a server bug or version incompatibility that omits the resume token; network-level data corruption.

Common situations: Adding a $project stage to the change stream pipeline that excludes _id; adding a $replaceRoot stage that changes the document structure; using a pipeline that transforms the change document shape before it reaches the driver's resume-token logic.

Related errors


AI-assisted analysis of mongodb/node-mongodb-native@dce7939f86 (2026-08-11). Data as JSON: /api/errors/87d654e55906092c. Report an issue: GitHub.

Appendix: 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 dce7939f86)