Automattic/mongoose · error · MongooseError
Cannot call on() on errored ChangeStream
Error message
Cannot call on() on errored ChangeStream
What it means
ChangeStream sets this.errored after a fatal driver-level failure. on() against an errored stream throws immediately: handlers attached to a dead stream would never fire, so Mongoose treats re-registration as a bug in the caller.
Source
Thrown at lib/cursor/changeStream.js:187
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();
return super.once(event, handler);
}
close() {
this.closed = true;
if (this.driverChangeStream) {
return this.driverChangeStream.close();
} else {View on GitHub (pinned to 49cdab0136)
Solutions
- Handle 'error' by closing the dead stream and creating a new one, then attach handlers to the new instance
- Guard: if (stream.errored || stream.closed) stream = await rebuildStream(); before stream.on(...)
- Centralize stream creation in one owner so recreation and re-attachment happen together
Example fix
// before
stream.on('change', sync); // re-registering after a failure throws
// after
stream.on('error', async err => {
await stream.close().catch(() => {});
stream = MyModel.watch(pipeline, opts);
stream.on('change', sync);
}); Defensive patterns
Strategy: validation
Validate before calling
function onSafe(stream, event, handler) {
if (stream.errored || stream.closed) stream = makeStream();
stream.on(event, handler);
return stream;
} Type guard
const isUsableChangeStream = (s) => s != null && typeof s.on === 'function' && !s.errored && !s.closed;
Prevention
- Attach an 'error' handler as the first listener on every change stream
- Recreate streams on fatal errors instead of re-registering handlers on them
- Log stream.errored state transitions to catch failover-related breakage early
When it happens
Trigger: Calling stream.on('change', fn) after the stream has emitted a fatal 'error' — common in reconnection code paths or when listener setup happens lazily and races with a failure.
Common situations: Event-driven consumers re-attaching 'change'/'error' handlers after failovers; plugin lifecycle code calling on() during teardown/restart of a watcher.
Related errors
- Cannot call hasNext() on errored ChangeStream
- Cannot call next() on errored ChangeStream
- Cannot call addListener() 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/c1c0342af4d13de1.
Report an issue: GitHub.