Automattic/mongoose · error · MongooseError

Cannot call addListener() on errored ChangeStream

Error message

Cannot call addListener() on errored ChangeStream

What it means

ChangeStream marks itself errored when the underlying driver stream fails fatally. addListener() on an errored stream throws immediately because binding new handlers to a dead stream would silently never fire, so Mongoose rejects the call.

Source

Thrown at lib/cursor/changeStream.js:179

    if (this.driverChangeStream != null) {
      return this.driverChangeStream.next(cb);
    }

    return this.$driverChangeStreamPromise.then(
      () => this.driverChangeStream.next(cb),
      err => {
        if (cb != null) {
          return cb(err);
        }
        throw err;
      }
    );
  }

  addListener(event, handler) {
    if (this.errored) {
      throw new MongooseError('Cannot call addListener() on errored ChangeStream');
    }
    this._bindEvents();
    return super.addListener(event, handler);
  }

  on(event, handler) {
    if (this.errored) {
      throw new MongooseError('Cannot call on() on errored ChangeStream');
    }
    this._bindEvents();
    return super.on(event, handler);
  }

  once(event, handler) {
    if (this.errored) {
      throw new MongooseError('Cannot call once() on errored ChangeStream');
    }
    this._bindEvents();

View on GitHub (pinned to 49cdab0136)

Solutions

  1. On stream 'error', close() the errored stream and create a fresh one before attaching listeners
  2. Guard registration: if (stream.errored || stream.closed) stream = await rebuildStream(); then addListener()
  3. Prefer on()/once() on a newly created stream rather than reusing stream instances after errors

Example fix

// before
function attach(stream) {
  stream.addListener('change', sync); // throws if the stream already errored
}

// after
function attach(stream) {
  if (stream.errored || stream.closed) stream = MyModel.watch(pipeline, opts);
  stream.addListener('change', sync);
}
Defensive patterns

Strategy: validation

Validate before calling

function attach(stream, event, handler) {
  if (stream.errored || stream.closed) stream = makeStream(); // fresh stream first
  stream.addListener(event, handler);
  return stream;
}

Type guard

const isUsableChangeStream = (s) =>
  s != null && typeof s.on === 'function' && !s.errored && !s.closed;

Prevention

When it happens

Trigger: Calling stream.addListener('change', fn) (or any event) after the stream emitted a fatal 'error' event — typically in reconnection code that re-attaches handlers to the same stream object.

Common situations: Reconnect wrappers that re-register event handlers after a failure; modular code where listener setup races with a stream error during a replica-set failover.

Related errors


AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21). Data as JSON: /api/errors/ba8c765134ea55fa. Report an issue: GitHub.