mongodb/node-mongodb-native · error · MongoRuntimeError
Unexpected null session. A cursor creating command should ha
Error message
Unexpected null session. A cursor creating command should have set this
What it means
Thrown by AbstractCursor.getMore() when cursorSession is null at getMore time. The session is created in cursorInit() (abstract_cursor.ts:910) if not already present. Because fetchBatch() initializes the cursor before calling getMore(), a null session here implies cursorInit() was bypassed or the session was cleared (e.g. by a prior cleanup()) without the cursor being marked dead/closed.
Source
Thrown at src/cursor/abstract_cursor.ts:872
protected abstract _initialize(
session: ClientSession | undefined
): Promise<InitialCursorResponse>;
/** @internal */
async getMore(): Promise<CursorResponse> {
if (this.cursorId == null) {
throw new MongoRuntimeError(
'Unexpected null cursor id. A cursor creating command should have set this'
);
}
if (this.selectedServer == null) {
throw new MongoRuntimeError(
'Unexpected null selectedServer. A cursor creating command should have set this'
);
}
if (this.cursorSession == null) {
throw new MongoRuntimeError(
'Unexpected null session. A cursor creating command should have set this'
);
}
const getMoreOptions = {
...this.cursorOptions,
session: this.cursorSession,
batchSize: this.cursorOptions.batchSize
};
const getMoreOperation = new GetMoreOperation(
this.cursorNamespace,
this.cursorId,
this.selectedServer,
getMoreOptions
);
return await executeOperation(this.cursorClient, getMoreOperation, this.timeoutContext);
}View on GitHub (pinned to 3366c21a63)
Solutions
- Avoid concurrent iteration and close() on the same cursor; serialize access with an async mutex or stop iteration before closing
- Use the public iteration APIs exclusively so cursorInit() always runs first
- If subclassing, never null cursorSession yourself; let cleanup() manage it
Example fix
// before
const cursor = coll.find();
await Promise.all([cursor.next(), cursor.close()]); // race
// after
const cursor = coll.find();
try {
for await (const doc of cursor) handle(doc);
} finally {
await cursor.close();
} Defensive patterns
Strategy: try-catch
Try / catch
try {
for await (const doc of cursor) handle(doc);
} catch (e) {
if (e instanceof MongoRuntimeError && /null session/.test(e.message)) {
// cursor was closed concurrently; recreate it
cursor = coll.find(filter);
} else throw e;
} Prevention
- Never iterate and close() the same cursor concurrently
- Serialize cursor access with an async mutex if shared
- Let cleanup() manage cursorSession; do not null it manually
When it happens
Trigger: Calling the internal getMore() after cleanup() has nulled cursorSession but before isClosed reflects it, or a custom subclass that does not delegate to the base cursorInit(). Concurrent close()+iteration races can also null the session between the guard and the call.
Common situations: Concurrent iteration and close() on the same cursor without external synchronization, or test code that manually nulls internal fields.
Related errors
- Unexpected null cursor id. A cursor creating command should
- Unexpected null selectedServer. A cursor creating command sh
- Unexpected null session. A cursor creating command should ha
- Attempted illegal state transition from [${this.state}] to [
- illegal state transition from [${target.s.state}] => [${newS
AI-assisted analysis of mongodb/node-mongodb-native@3366c21a63 (2026-08-04).
Data as JSON: /data/errors/3d89ded6b683147c.json.
Report an issue: GitHub.