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
- After an 'error', close() the stream and create a fresh one with Model.watch() before calling once()
- Check stream.errored / stream.closed before registering one-shot listeners
- 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
- Arm one-shot listeners only on freshly created streams
- Build retries around stream recreation, not re-arming listeners on dead streams
- Close errored streams before discarding them to free driver resources
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
- Cannot call hasNext() on errored ChangeStream
- Cannot call next() on errored ChangeStream
- Cannot call addListener() on errored ChangeStream
- Cannot call on() 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/e093beff21957883.
Report an issue: GitHub.