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

  1. Avoid concurrent iteration and close() on the same cursor; serialize access with an async mutex or stop iteration before closing
  2. Use the public iteration APIs exclusively so cursorInit() always runs first
  3. 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

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


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