Automattic/mongoose · error · MongooseError

Cannot call once() on errored ChangeStream

Error message

Cannot call once() on errored ChangeStream

What it means

ChangeStream flips this.errored on a fatal underlying failure. once() on an errored stream throws immediately because a one-shot listener on a dead stream would never fire; Mongoose surfaces the misuse instead of letting it pass silently.

Source

Thrown at lib/cursor/changeStream.js:195

  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();
    return super.once(event, handler);
  }

  close() {
    this.closed = true;
    if (this.driverChangeStream) {
      return this.driverChangeStream.close();
    } else {
      return this.$driverChangeStreamPromise.then(
        () => this.driverChangeStream.close(),
        () => {} // No need to close if opening the change stream failed
      );
    }
  }
}

View on GitHub (pinned to 49cdab0136)

Solutions

  1. After an 'error', close() the stream and create a fresh one with Model.watch() before calling once()
  2. Check stream.errored / stream.closed before registering one-shot listeners
  3. Restructure retries around stream recreation rather than re-arming listeners on a dead stream

Example fix

// before
stream.once('change', probe); // throws if the stream has errored

// after
if (stream.errored || stream.closed) {
  await stream.close().catch(() => {});
  stream = MyModel.watch(pipeline, opts);
}
stream.once('change', probe);
Defensive patterns

Strategy: validation

Validate before calling

function onceSafe(stream, event, handler) {
  if (stream.errored || stream.closed) stream = makeStream();
  stream.once(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.once('change', fn) or stream.once('error', fn) after the stream already errored fatally — e.g. setting up a retry probe on the same stream instance.

Common situations: Retry logic that arms a one-shot listener for the next event after a failure; graceful-shutdown or health-check code calling once() on a stream that died during a failover.

Related errors


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