{"id":"87d654e55906092c","repo":"mongodb/node-mongodb-native","slug":"a-change-stream-document-has-been-received-that-la","errorCode":null,"errorMessage":"A change stream document has been received that lacks a resume token (_id).","messagePattern":"A change stream document has been received that lacks a resume token \\(_id\\)\\.","errorType":"exception","errorClass":"MongoChangeStreamError","httpStatus":null,"severity":"error","filePath":"src/change_stream.ts","lineNumber":997,"sourceCode":"    this.cursorStream?.destroy();\n    this.cursorStream = undefined;\n  }\n\n  /** @internal */\n  private _processChange(change: TChange | null): TChange {\n    if (this.isClosed) {\n      // TODO(NODE-3485): Replace with MongoChangeStreamClosedError\n      throw new MongoAPIError(CHANGESTREAM_CLOSED_ERROR);\n    }\n\n    // a null change means the cursor has been notified, implicitly closing the change stream\n    if (change == null) {\n      // TODO(NODE-3485): Replace with MongoChangeStreamClosedError\n      throw new MongoRuntimeError(CHANGESTREAM_CLOSED_ERROR);\n    }\n\n    if (change && !change._id) {\n      throw new MongoChangeStreamError(NO_RESUME_TOKEN_ERROR);\n    }\n\n    // cache the resume token\n    this.cursor.cacheResumeToken(change._id);\n\n    // wipe the startAtOperationTime if there was one so that there won't be a conflict\n    // between resumeToken and startAtOperationTime if we need to reconnect the cursor\n    this.options.startAtOperationTime = undefined;\n\n    return change;\n  }\n\n  /** @internal */\n  private _processErrorStreamMode(changeStreamError: AnyError, cursorInitialized: boolean) {\n    // If the change stream has been closed explicitly, do not process error.\n    if (this.isClosed) return;\n\n    if (","sourceCodeStart":979,"sourceCodeEnd":1015,"githubUrl":"https://github.com/mongodb/node-mongodb-native/blob/3366c21a6311e02f1be91da982f9b93d3cce99a0/src/change_stream.ts#L979-L1015","documentation":"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.","triggerScenarios":"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().","commonSituations":"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.","solutions":["Inspect the pipeline passed to watch(); ensure no stage removes or renames the change document's _id field.","If you must transform documents, do so in your application after receiving the change, not in the $changeStream pipeline.","Upgrade mongod to a patched version if you have confirmed the pipeline is correct and the server is omitting _id."],"exampleFix":"// before\nconst cs = collection.watch([\n  { $project: { _id: 0, fullDocument: 1 } } // strips resume token\n]);\n\n// after\nconst cs = collection.watch([]);\ncs.on('change', change => {\n  const { fullDocument } = change; // transform in app code\n});","handlingStrategy":"validation","validationCode":"// Ensure the watch pipeline does not strip _id\nfunction assertPipelinePreservesResumeToken(pipeline) {\n  for (const stage of pipeline) {\n    if (stage.$project && stage.$project._id === 0) throw new Error('pipeline removes resume token _id');\n    if (stage.$unset && (stage.$unset.includes('_id'))) throw new Error('pipeline removes resume token _id');\n  }\n}","typeGuard":"// Not applicable: validates aggregation stage shape rather than a TS type.","tryCatchPattern":"cs.on('error', err => {\n  if (err instanceof MongoChangeStreamError && /lacks a resume token/.test(err.message)) {\n    // audit and fix the pipeline, then reopen\n  } else throw err;\n});","preventionTips":["Keep the $changeStream pipeline free of stages that drop _id.","Transform change documents in application code, not in the watch pipeline.","Add a test asserting the watch pipeline does not project away _id."],"tags":["change-streams","aggregation","resume-token","api-misuse"],"analyzedSha":"3366c21a6311e02f1be91da982f9b93d3cce99a0","analyzedAt":"2026-08-04T13:40:15.335Z","schemaVersion":2}