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
- On stream 'error', close() the errored stream and create a fresh one before attaching listeners
- Guard registration: if (stream.errored || stream.closed) stream = await rebuildStream(); then addListener()
- 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
- Register all listeners right after creating the stream, before errors can occur
- On 'error', rebuild the stream and re-attach listeners to the new instance
- Keep listener registration in one place so it cannot race with stream death
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
- Cannot call hasNext() on errored ChangeStream
- Cannot call next() on errored ChangeStream
- Cannot call on() on errored ChangeStream
- Cannot call once() on errored ChangeStream
- Cannot create change stream with `hydrate: true` unless call
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/ba8c765134ea55fa.
Report an issue: GitHub.