mongodb/node-mongodb-native · error · MongoUnexpectedServerResponseError

Server ended moreToCome unexpectedly

Error message

Server ended moreToCome unexpectedly

What it means

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.

Source

Thrown at src/cmap/connection.ts:676

      return document;
    }
    throw new MongoUnexpectedServerResponseError('Unable to get response from server');
  }

  public exhaustCommand(
    ns: MongoDBNamespace,
    command: Document,
    options: CommandOptions,
    replyListener: Callback
  ) {
    const exhaustLoop = async () => {
      this.throwIfAborted();
      for await (const reply of this.sendCommand(ns, command, options)) {
        replyListener(undefined, reply);
        this.throwIfAborted();
      }
      throw new MongoUnexpectedServerResponseError('Server ended moreToCome unexpectedly');
    };

    exhaustLoop().then(undefined, replyListener);
  }

  private throwIfAborted() {
    if (this.error) throw this.error;
  }

  /**
   * @internal
   *
   * Writes an OP_MSG or OP_QUERY request to the socket, optionally compressing the command. This method
   * waits until the socket's buffer has emptied (the Nodejs socket `drain` event has fired).
   */
  private async writeCommand(
    command: WriteProtocolMessageType,
    options: {

View on GitHub (pinned to 3366c21a63)

Solutions

  1. Wrap change-stream / exhaust consumers in a reconnect loop that re-issues the request on error.
  2. Ensure replica set high availability so failovers are handled by SDAM and the cursor resumes.
  3. For change streams, use tryNext() / hasNext() with error handling that restarts the stream at the last resumeToken.
  4. Check server stability - frequent exhaust termination often indicates an unstable deployment.

Example fix

// before
const cs = coll.watch();
for await (const c of cs) handle(c); // throws on failover, dies

// after
async function run() {
  let cs = coll.watch();
  while (true) {
    try { for await (const c of cs) handle(c); }
    catch (e) { cs = coll.watch([], { resumeAfter: lastToken }); }
  }
}
Defensive patterns

Strategy: retry

Type guard

import { MongoUnexpectedServerResponseError } from 'mongodb';
function isExhaustTerminated(e: unknown): e is MongoUnexpectedServerResponseError {
  return e instanceof MongoUnexpectedServerResponseError &&
    /moreToCome/.test(e.message);
}

Try / catch

import { MongoUnexpectedServerResponseError } from 'mongodb';
async function watchForever(coll: any, onChange: (c: any) => void) {
  let resumeToken: any;
  while (true) {
    const stream = coll.watch([], resumeToken ? { resumeAfter: resumeToken } : {});
    try {
      for await (const c of stream) { resumeToken = c._id; onChange(c); }
    } catch (e) {
      if (e instanceof MongoUnexpectedServerResponseError) continue;
      throw e;
    } finally { await stream.close().catch(() => {}); }
  }
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of mongodb/node-mongodb-native@3366c21a63 (2026-08-04). Data as JSON: /data/errors/3a7afb3e88866265.json. Report an issue: GitHub.