{"id":"526748112b254bf1","repo":"mongodb/node-mongodb-native","slug":"changestream-is-closed","errorCode":null,"errorMessage":"ChangeStream is closed","messagePattern":"ChangeStream is closed","errorType":"exception","errorClass":"MongoChangeStreamError","httpStatus":null,"severity":"error","filePath":"src/change_stream.ts","lineNumber":870,"sourceCode":"    const cursor = this.cursor;\n    try {\n      await cursor.close();\n    } finally {\n      this._endStream();\n    }\n  }\n\n  /**\n   * Return a modified Readable stream including a possible transform method.\n   *\n   * NOTE: When using a Stream to process change stream events, the stream will\n   * NOT automatically resume in the case a resumable error is encountered.\n   *\n   * @throws MongoChangeStreamError if the underlying cursor or the change stream is closed\n   */\n  stream(): Readable & AsyncIterable<TChange> {\n    if (this.closed) {\n      throw new MongoChangeStreamError(CHANGESTREAM_CLOSED_ERROR);\n    }\n\n    return this.cursor.stream();\n  }\n\n  /** @internal */\n  private _setIsEmitter(): void {\n    if (this.mode === 'iterator') {\n      // TODO(NODE-3485): Replace with MongoChangeStreamModeError\n      throw new MongoAPIError(\n        'ChangeStream cannot be used as an EventEmitter after being used as an iterator'\n      );\n    }\n    this.mode = 'emitter';\n  }\n\n  /** @internal */\n  private _setIsIterator(): void {","sourceCodeStart":852,"sourceCodeEnd":888,"githubUrl":"https://github.com/mongodb/node-mongodb-native/blob/3366c21a6311e02f1be91da982f9b93d3cce99a0/src/change_stream.ts#L852-L888","documentation":"Thrown by ChangeStream.stream() when you attempt to obtain a Readable stream from a change stream that has already been closed (this.closed === true, set by close()). It is a MongoChangeStreamError with the literal message 'ChangeStream is closed'. The guard exists because the underlying cursor has already been torn down and piping it would produce undefined behavior.","triggerScenarios":"Calling changeStream.stream() after await changeStream.close(), after the 'close' event has fired, or after the stream auto-closed due to an unrecoverable error in emitter mode.","commonSituations":"Code that caches a ChangeStream reference and later calls .stream() without checking liveness; reuse of a stream across reconnect cycles; calling .stream() inside a 'close' or 'end' event handler.","solutions":["Create a fresh ChangeStream via collection.watch()/db.watch() before calling .stream() instead of reusing the closed one.","Track the close state yourself (listen for the 'close' event) and guard the .stream() call.","If you only need events, register listeners once on a long-lived ChangeStream rather than re-deriving a Node stream."],"exampleFix":"// before\nconst cs = collection.watch(pipeline);\nawait cs.close();\nconst nodeStream = cs.stream(); // throws\n\n// after\nconst cs = collection.watch(pipeline);\nconst nodeStream = cs.stream(); // obtain BEFORE closing","handlingStrategy":"type-guard","validationCode":"// Before calling .stream(), guard on liveness\nimport { MongoChangeStreamError } from 'mongodb';\nfunction streamIfOpen(cs) {\n  if (cs.closed) throw new MongoChangeStreamError('ChangeStream is closed');\n  return cs.stream();\n}","typeGuard":"// closed is a public readonly boolean on ChangeStream\nfunction isOpen(cs): cs is import('mongodb').ChangeStream {\n  return !cs.closed;\n}","tryCatchPattern":"try {\n  const s = cs.stream();\n} catch (err) {\n  if (err instanceof MongoChangeStreamError && err.message === 'ChangeStream is closed') {\n    // recreate the change stream instead\n  } else throw err;\n}","preventionTips":["Obtain .stream() immediately after collection.watch() and before any close path.","Track the 'close' event to invalidate cached references.","Never reuse a ChangeStream across reconnect cycles; create a new one."],"tags":["change-streams","lifecycle","streaming"],"analyzedSha":"3366c21a6311e02f1be91da982f9b93d3cce99a0","analyzedAt":"2026-08-04T13:40:15.335Z","schemaVersion":2}