mongodb/node-mongodb-native · error · MongoRuntimeError
Unable to iterate cursor with no id
Error message
Unable to iterate cursor with no id
What it means
Thrown as a MongoRuntimeError by GetMoreOperation.buildCommand when the cursorId is null or zero (this.cursorId.isZero()). A zero cursor id means the server has already closed the cursor (it returned id 0 indicating no more batches), so issuing getMore is invalid. This usually surfaces as an attempt to iterate an already-exhausted or closed cursor.
Solutions
- Stop iterating once the cursor is exhausted (for-await-of ends naturally; check (await cursor.next()) === null).
- Do not reuse a cursor after it has been closed or fully iterated; create a new query instead.
- Avoid concurrent iteration of the same cursor.
Example fix
// before
const cursor = coll.find({});
await cursor.forEach(()=>{});
await cursor.next(); // cursor exhausted -> error
// after
const cursor = coll.find({});
await cursor.forEach(()=>{});
// start a fresh query if you need the data again
const fresh = coll.find({}); Defensive patterns
Strategy: validation
Validate before calling
import type { FindCursor } from 'mongodb';
async function safelyNext<T>(cursor: FindCursor<T>): Promise<T | null> {
// Respect cursor exhaustion; do not call next after the cursor reports closed
if (cursor.closed) return null;
return cursor.next();
} Try / catch
try {
await cursor.next();
} catch (err) {
if (err instanceof MongoRuntimeError && /no id/.test(err.message)) {
// cursor already exhausted; stop iterating
} else throw err;
} Prevention
- Use for-await-of to iterate; it stops naturally when the cursor is drained.
- Do not call .next() after forEach/toArray completes; start a new query instead.
- Never iterate the same cursor concurrently.
When it happens
Trigger: Continuing to call cursor.next() / for-await after the cursor has been fully drained (server returned cursorId 0), or calling .next() on a cursor that was explicitly closed. The check is at get_more.ts:57.
Common situations: Looping over a cursor without checking for done; reusing a cursor after iteration completed; concurrency where one consumer closes the cursor while another iterates; transforming a cursor to an array then iterating again.
Related errors
- A collection name must be determined before getMore
- A collection name must be determined before killCursors
- Cursor is exhausted
- An unexpected error type
- Argument for maxTimeMS must be a number
AI-assisted analysis of mongodb/node-mongodb-native@dce7939f86 (2026-08-11).
Data as JSON: /api/errors/b5ba08685a791729.
Report an issue: GitHub.
Appendix: source
Thrown at src/operations/get_more.ts:58
cursorId: Long;
override options: GetMoreOptions;
constructor(ns: MongoDBNamespace, cursorId: Long, server: Server, options: GetMoreOptions) {
super(options);
this.options = options;
this.ns = ns;
this.cursorId = cursorId;
this.server = server;
}
override get commandName() {
return 'getMore' as const;
}
override buildCommand(_connection: Connection): Document {
if (this.cursorId == null || this.cursorId.isZero()) {
throw new MongoRuntimeError('Unable to iterate cursor with no id');
}
const collection = this.ns.collection;
if (collection == null) {
// Cursors should have adopted the namespace returned by MongoDB
// which should always defined a collection name (even a pseudo one, ex. db.aggregate())
throw new MongoRuntimeError('A collection name must be determined before getMore');
}
const getMoreCmd: GetMoreCommand = {
getMore: this.cursorId,
collection
};
if (typeof this.options.batchSize === 'number') {
getMoreCmd.batchSize = Math.abs(this.options.batchSize);
}
View on GitHub (pinned to dce7939f86)