mongodb/node-mongodb-native · error · MongoCursorExhaustedError

Cursor is exhausted

Error message

Cursor is exhausted

What it means

MongoCursorExhaustedError thrown by cursor.next() when cursorId === Long.ZERO, meaning the server has already signaled that the cursor is fully drained and no more batches are forthcoming. Calling next() on an already-exhausted cursor is a logic error because there is nothing left to fetch.

Source

Thrown at src/cursor/abstract_cursor.ts:553

          return true;
        }
        await this.fetchBatch();
      } while (!this.isDead || (this.documents?.length ?? 0) !== 0);
    } finally {
      if (this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION) {
        this.timeoutContext?.clear();
      }
    }

    return false;
  }

  /** Get the next available document from the cursor, returns null if no more documents are available. */
  async next(): Promise<TSchema | null> {
    this.signal?.throwIfAborted();

    if (this.cursorId === Long.ZERO) {
      throw new MongoCursorExhaustedError();
    }

    if (this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && this.cursorId != null) {
      this.timeoutContext?.refresh();
    }

    try {
      do {
        const doc = this.documents?.shift(this.deserializationOptions);
        if (doc != null) {
          if (this.transform != null) return await this.transformDocument(doc);
          return doc;
        }
        await this.fetchBatch();
      } while (!this.isDead || (this.documents?.length ?? 0) !== 0);
    } finally {
      if (this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION) {
        this.timeoutContext?.clear();

View on GitHub (pinned to 3366c21a63)

Solutions

  1. Stop calling next() once it returns null — that signals exhaustion.
  2. Use `for await (const doc of cursor)` which handles termination cleanly.
  3. Track a local `exhausted` flag and skip further next() calls.

Example fix

// before
while (true) { const d = await cursor.next(); use(d); } // throws after drain
// after
for await (const d of cursor) { use(d); }
Defensive patterns

Strategy: try-catch

Try / catch

try {
  const doc = await cursor.next();
  if (doc === null) return; // graceful end
} catch (e) {
  if (e instanceof MongoCursorExhaustedError) return;
  throw e;
}

Prevention

When it happens

Trigger: Calling await cursor.next() after iteration completed, after a previous next() returned null, after cursor.close(), or after the server returned a zero cursorId on the initial find/getMore. Common in polling loops that do not break on null.

Common situations: While/for loops that ignore a previous null return and call next() again; reusing a cursor stored in a long-lived object after it drained; race where two consumers drain the same cursor.

Related errors


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