mongodb/node-mongodb-native · error · MongoCursorInUseError
Cursor is already initialized
Error message
Cursor is already initialized
What it means
Thrown as a MongoCursorInUseError (default message 'Cursor is already initialized') from throwIfInitialized() once `this.initialized` is true. Initialization becomes true during the first cursorInit() (set even if the first command fails), after which the cursor has a server-side cursor id, a session, and possibly a timeout context. Many configuration methods (addCursorFlag, map, withReadPreference, withReadConcern, maxTimeMS, batchSize, addStage) call throwIfInitialized() to forbid changing options after the wire command has already been sent, because changing them would have no effect or desync the client from the server.
Solutions
- Set all options before the first call that triggers iteration (next/hasNext/toArray/for-await).
- If the cursor has already run, create a new cursor via collection.find()/aggregate() with the desired options.
- Use cursor.rewind() (when allowed) to reset to the uninitialized state, then reconfigure before iterating again.
- Build the full options object up front instead of chaining config calls across await boundaries.
Example fix
// before: config after iteration started
const cursor = collection.find({});
await cursor.next();
cursor.batchSize(50); // throws MongoCursorInUseError
// after: configure before iterating
const cursor = collection.find({}, { batchSize: 50 });
await cursor.next(); Defensive patterns
Strategy: validation
Validate before calling
// Configure fully before iterating; recreate (or rewind) to change options later
function buildCursor(filter: object, opts: FindOptions) {
const cursor = collection.find(filter, opts); // all options up front
// do NOT call addCursorFlag/batchSize/maxTimeMS/map after any await on cursor
return cursor;
} Try / catch
try {
cursor.batchSize(50);
} catch (err) {
if (err instanceof MongoCursorInUseError) {
// cursor already started: build a fresh one with the new options
cursor = collection.find(filter, { batchSize: 50 });
} else {
throw err;
}
} Prevention
- Set every option before the first call that triggers iteration.
- Create a new cursor instead of reconfiguring a started one.
- Use rewind() only when no shared timeout context is in play.
- Build the options object completely up front.
When it happens
Trigger: Calling cursor.addCursorFlag(...), cursor.batchSize(...), cursor.maxTimeMS(...), cursor.withReadPreference(...), cursor.map(...), or aggregation cursor.addStage(...) after cursor.next(), hasNext(), tryNext(), toArray(), or a for-await loop has started; calling a config method after an earlier iteration attempt failed (initialized is set even on failure).
Common situations: Configuring a cursor lazily inside an iteration loop; reusing a cursor variable and trying to change options between runs instead of creating a new cursor; calling map() after a partial read; the first command failed and the user retries with a tweaked option on the same cursor instance.
Related errors
- Cursor is exhausted
- Cursor returned a `null` document, but the cursor is not…
- A collection name must be determined before getMore
- A collection name must be determined before killCursors
- Argument for maxTimeMS must be a number
AI-assisted analysis of mongodb/node-mongodb-native@dce7939f86 (2026-08-11).
Data as JSON: /api/errors/acc895fcdff9d4a5.
Report an issue: GitHub.
Appendix: source
Thrown at src/cursor/abstract_cursor.ts:1091
if (transformedDocument === null) {
const TRANSFORM_TO_NULL_ERROR =
'Cursor returned a `null` document, but the cursor is not exhausted. Mapping documents to `null` is not supported in the cursor transform.';
throw new MongoAPIError(TRANSFORM_TO_NULL_ERROR);
}
return transformedDocument;
} catch (transformError) {
try {
await this.close();
} catch (closeError) {
squashError(closeError);
}
throw transformError;
}
}
/** @internal */
protected throwIfInitialized() {
if (this.initialized) throw new MongoCursorInUseError();
}
}
class ReadableCursorStream extends Readable {
private _cursor: AbstractCursor;
private _readInProgress = false;
constructor(cursor: AbstractCursor) {
super({
objectMode: true,
autoDestroy: false,
highWaterMark: 1
});
this._cursor = cursor;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
override _read(size: number): void {View on GitHub (pinned to dce7939f86)