{"id":"3a7afb3e88866265","repo":"mongodb/node-mongodb-native","slug":"server-ended-moretocome-unexpectedly","errorCode":null,"errorMessage":"Server ended moreToCome unexpectedly","messagePattern":"Server ended moreToCome unexpectedly","errorType":"exception","errorClass":"MongoUnexpectedServerResponseError","httpStatus":null,"severity":"error","filePath":"src/cmap/connection.ts","lineNumber":676,"sourceCode":"\n      return document;\n    }\n    throw new MongoUnexpectedServerResponseError('Unable to get response from server');\n  }\n\n  public exhaustCommand(\n    ns: MongoDBNamespace,\n    command: Document,\n    options: CommandOptions,\n    replyListener: Callback\n  ) {\n    const exhaustLoop = async () => {\n      this.throwIfAborted();\n      for await (const reply of this.sendCommand(ns, command, options)) {\n        replyListener(undefined, reply);\n        this.throwIfAborted();\n      }\n      throw new MongoUnexpectedServerResponseError('Server ended moreToCome unexpectedly');\n    };\n\n    exhaustLoop().then(undefined, replyListener);\n  }\n\n  private throwIfAborted() {\n    if (this.error) throw this.error;\n  }\n\n  /**\n   * @internal\n   *\n   * Writes an OP_MSG or OP_QUERY request to the socket, optionally compressing the command. This method\n   * waits until the socket's buffer has emptied (the Nodejs socket `drain` event has fired).\n   */\n  private async writeCommand(\n    command: WriteProtocolMessageType,\n    options: {","sourceCodeStart":658,"sourceCodeEnd":694,"githubUrl":"https://github.com/mongodb/node-mongodb-native/blob/3366c21a6311e02f1be91da982f9b93d3cce99a0/src/cmap/connection.ts#L658-L694","documentation":"Thrown at the end of exhaustCommand's loop (src/cmap/connection.ts:676) when an exhaust cursor (getMore with awaitData/streaming) unexpectedly signaled the end of the stream - the server set moreToCome=false (or closed the socket) without the client requesting closure. Exhaust cursors are expected to keep sending; an unsolicited stop is treated as a protocol violation.","triggerScenarios":"Using an exhaust cursor (change streams in some configurations, or explicit exhaust:true) where the server stopped sending responses. Triggered inside the exhaustLoop closure after the for-await ends normally (src/cmap/connection.ts:670-677).","commonSituations":"Server failover during an exhaust stream (primary step-down); server restart; network interruption that closed the socket cleanly; change stream against a deployment that invalidated the cursor; explicit cursor.close() racing with the exhaust loop.","solutions":["Wrap change-stream / exhaust consumers in a reconnect loop that re-issues the request on error.","Ensure replica set high availability so failovers are handled by SDAM and the cursor resumes.","For change streams, use tryNext() / hasNext() with error handling that restarts the stream at the last resumeToken.","Check server stability - frequent exhaust termination often indicates an unstable deployment."],"exampleFix":"// before\nconst cs = coll.watch();\nfor await (const c of cs) handle(c); // throws on failover, dies\n\n// after\nasync function run() {\n  let cs = coll.watch();\n  while (true) {\n    try { for await (const c of cs) handle(c); }\n    catch (e) { cs = coll.watch([], { resumeAfter: lastToken }); }\n  }\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":"import { MongoUnexpectedServerResponseError } from 'mongodb';\nfunction isExhaustTerminated(e: unknown): e is MongoUnexpectedServerResponseError {\n  return e instanceof MongoUnexpectedServerResponseError &&\n    /moreToCome/.test(e.message);\n}","tryCatchPattern":"import { MongoUnexpectedServerResponseError } from 'mongodb';\nasync function watchForever(coll: any, onChange: (c: any) => void) {\n  let resumeToken: any;\n  while (true) {\n    const stream = coll.watch([], resumeToken ? { resumeAfter: resumeToken } : {});\n    try {\n      for await (const c of stream) { resumeToken = c._id; onChange(c); }\n    } catch (e) {\n      if (e instanceof MongoUnexpectedServerResponseError) continue;\n      throw e;\n    } finally { await stream.close().catch(() => {}); }\n  }\n}","preventionTips":["Always wrap change-stream consumers in a reconnect loop with resumeAfter.","Persist resume tokens externally so restarts resume without data loss.","Use a replica set or sharded cluster for stable change streams."],"tags":["exhaust","change-stream","cursor","unexpected-response"],"analyzedSha":"3366c21a6311e02f1be91da982f9b93d3cce99a0","analyzedAt":"2026-08-04T13:40:15.335Z","schemaVersion":2}