{"record":{"id":"e093beff21957883","repo":"Automattic/mongoose","slug":"cannot-call-once-on-errored-changestream","errorCode":null,"errorMessage":"Cannot call once() on errored ChangeStream","messagePattern":"Cannot call once\\(\\) on errored ChangeStream","errorType":"exception","errorClass":"MongooseError","httpStatus":null,"severity":"error","filePath":"lib/cursor/changeStream.js","lineNumber":195,"sourceCode":"  addListener(event, handler) {\n    if (this.errored) {\n      throw new MongooseError('Cannot call addListener() on errored ChangeStream');\n    }\n    this._bindEvents();\n    return super.addListener(event, handler);\n  }\n\n  on(event, handler) {\n    if (this.errored) {\n      throw new MongooseError('Cannot call on() on errored ChangeStream');\n    }\n    this._bindEvents();\n    return super.on(event, handler);\n  }\n\n  once(event, handler) {\n    if (this.errored) {\n      throw new MongooseError('Cannot call once() on errored ChangeStream');\n    }\n    this._bindEvents();\n    return super.once(event, handler);\n  }\n\n  close() {\n    this.closed = true;\n    if (this.driverChangeStream) {\n      return this.driverChangeStream.close();\n    } else {\n      return this.$driverChangeStreamPromise.then(\n        () => this.driverChangeStream.close(),\n        () => {} // No need to close if opening the change stream failed\n      );\n    }\n  }\n}\n","sourceCodeStart":177,"sourceCodeEnd":213,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/cursor/changeStream.js#L177-L213","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nstream.once('change', probe); // throws if the stream has errored\n\n// after\nif (stream.errored || stream.closed) {\n  await stream.close().catch(() => {});\n  stream = MyModel.watch(pipeline, opts);\n}\nstream.once('change', probe);","handlingStrategy":"validation","validationCode":"function onceSafe(stream, event, handler) {\n  if (stream.errored || stream.closed) stream = makeStream();\n  stream.once(event, handler);\n  return stream;\n}","typeGuard":"const isUsableChangeStream = (s) =>\n  s != null && typeof s.on === 'function' && !s.errored && !s.closed;","tryCatchPattern":null,"preventionTips":["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"],"tags":["change-stream","once","error-recovery","mongoose"],"backgroundTag":"change-stream-resume-failed","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}