mongodb/node-mongodb-native · error · MongoUnexpectedServerResponseError
Server ended moreToCome unexpectedly
Error message
Server ended moreToCome unexpectedly
What it means
A MongoUnexpectedServerResponseError thrown in Connection.exhaustCommand when the sendCommand generator ends normally (no more data) while the driver expected the server to keep sending moreToCome batches. Exhaust cursors (used by change streams and getMore-style streaming) rely on the server continuously pushing replies; a clean end signals the stream was terminated prematurely.
Solutions
- Disable response buffering on any proxy in front of MongoDB.
- Use resume tokens to restart the change stream after the interruption.
- Check server logs for killCursors or cursor timeout events.
- Ensure the connection between driver and server is not terminated by an idle-timeout device.
Example fix
// before (no resume strategy)
const cs = coll.watch();
for await (const c of cs) handle(c);
// after (resume on unexpected end)
let resumeToken;
while (true) {
const cs = coll.watch([], { resumeAfter: resumeToken });
try {
for await (const c of cs) { resumeToken = c._id; handle(c); }
} catch (e) { /* reconnect */ }
} Defensive patterns
Strategy: retry
Type guard
function isExhaustEndedUnexpectedly(e: unknown): boolean {
return e?.constructor?.name === 'MongoUnexpectedServerResponseError' && /moreToCome/.test((e as any)?.message ?? '');
} Try / catch
let resumeToken;
while (true) {
const cs = coll.watch([], { resumeAfter: resumeToken });
try {
for await (const change of cs) { resumeToken = change._id; handle(change); }
} catch (e) {
if (isExhaustEndedUnexpectedly(e)) continue; // reconnect and resume
throw e;
}
} Prevention
- Disable response buffering on proxies in front of MongoDB.
- Persist resume tokens so change streams can resume after interruption.
- Use a heartbeat/keepalive to prevent idle-connection reaping.
When it happens
Trigger: Using exhaustCommand (change streams, certain legacy exhaust getMore paths) where the server stops sending moreToCome frames without the driver initiating cursor close. Happens when the server kills the cursor, the connection is reset mid-stream, or a proxy buffers/terminates the long-lived stream.
Common situations: Change streams behind a buffering proxy (nginx, envoy) with response buffering on; server-side cursor timeout or killCursors; network interruption during a long-running change stream; load balancers that do not support HTTP/1.1-style streaming for raw TCP.
Related errors
AI-assisted analysis of mongodb/node-mongodb-native@dce7939f86 (2026-08-11).
Data as JSON: /api/errors/3a7afb3e88866265.
Report an issue: GitHub.
Appendix: 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 dce7939f86)