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 by GetMoreOperation.buildCommand when the cursor being iterated has a null or zero cursor id. A zero id means no server-side cursor exists - either the initial batch already exhausted the result set, the cursor was already closed, or it was never successfully created. This is a MongoRuntimeError indicating invalid driver/cursor state rather than a bad user argument.
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 3366c21a63)
Solutions
- Do not reuse a cursor after exhaustion - re-issue the query/find/aggregate to obtain a fresh cursor.
- Ensure you fully consume a cursor exactly once (single toArray()/for-await loop).
- If iterating in pages, capture the cursor once and drive it linearly; don't hand the same cursor to concurrent consumers.
- On transient errors, restart the whole operation (new find/aggregate) rather than resuming the dead cursor.
Example fix
// before - reusing an exhausted cursor
cursor = collection.find({});
await cursor.toArray();
await cursor.next(); // throws: no id
// after - create a new cursor for each iteration
await collection.find({}).toArray(); Defensive patterns
Strategy: retry
Try / catch
try {
await cursor.next();
} catch (e) {
if (e instanceof MongoRuntimeError && /no id/.test(e.message)) {
// cursor is exhausted/dead - re-run the query to get a fresh cursor instead of resuming
cursor = collection.find(filter);
} else throw e;
} Prevention
- Never iterate the same cursor twice or from two concurrent consumers.
- Re-issue the query to obtain a fresh cursor after exhaustion or error.
- Consume cursors via a single for-await loop or a single toArray() call.
When it happens
Trigger: Iterating a cursor a second time after it was fully consumed or explicitly closed; iterating a cursor whose initial command returned id 0 (small result set returned in the first batch); a race where killCursors ran between batches; calling .next()/.toArray() on a cursor after an error already tore it down; manually reusing a cursor object across async boundaries.
Common situations: Storing a cursor in a shared variable and iterating it from two places; awaiting toArray() then calling toArray() again; tailable/change-stream cursors during shutdown; retry logic that re-enters a cursor loop after a transient failure without re-creating the cursor.
Related errors
- A collection name must be determined before getMore
- Cursor is exhausted
- Cannot rewind cursor that does not own its timeout context.
- Unexpected null cursor id. A cursor creating command should
- Unexpected null selectedServer. A cursor creating command sh
AI-assisted analysis of mongodb/node-mongodb-native@3366c21a63 (2026-08-04).
Data as JSON: /data/errors/b5ba08685a791729.json.
Report an issue: GitHub.