mongodb/node-mongodb-native · error · MongoAPIError

Cannot rewind cursor that does not own its timeout context.

Error message

Cannot rewind cursor that does not own its timeout context.

What it means

Thrown as a MongoAPIError from rewind() when the cursor has a timeoutContext whose owner is not this cursor (`this.timeoutContext.owner !== this`). When a cursor is created inside another operation (e.g. an aggregation run under a parent CSOT timeout context), the timeout context is shared/owned externally; rewinding would need to clear that context, but doing so would corrupt the parent's deadline. The driver therefore forbids rewinding a cursor that borrowed its timeout context. The check sits at the top of rewind(), before the not-initialized early return.

Solutions

  1. Do not call rewind() on cursors created under a CSOT/timeoutMS budget; create a fresh cursor via collection.find()/aggregate() instead.
  2. If you need to re-run the query, drop rewind() and build a new cursor with the same options.
  3. If you must rewind, ensure no timeoutMS/timeoutContext is in effect on the client or the cursor.

Example fix

// before: rewind under a shared timeout context
const cursor = collection.find(filter, { timeoutMS: 1000 });
await cursor.next();
cursor.rewind(); // throws if the cursor borrowed a timeout context

// after: create a fresh cursor instead of rewinding
const cursor2 = collection.find(filter, { timeoutMS: 1000 });
Defensive patterns

Strategy: validation

Validate before calling

// Avoid rewind() entirely under CSOT; create a fresh cursor instead
function rerun(cursor: FindCursor) {
  // safe: brand-new cursor with the same filter/options
  return cursor.client.db(cursor.namespace.db).collection(cursor.namespace.collection).find(filter, opts);
}

Try / catch

try {
  cursor.rewind();
} catch (err) {
  if (err instanceof MongoAPIError && /does not own its timeout context/.test(err.message)) {
    // create a fresh cursor instead of rewinding
    cursor = collection.find(filter, opts);
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Calling cursor.rewind() on a cursor whose timeoutContext was injected via the internal timeoutContext option (typically by the driver itself when nesting operations under CSOT); rewinding a cursor obtained from an operation that ran under an explicit timeoutMS on the client/command.

Common situations: Mixing manual rewind() usage with CSOT (timeoutMS) on a client configured with a global timeout; calling rewind() on a cursor returned by an internal helper that injected a shared timeout context.

Understand the failure class

Related errors


AI-assisted analysis of mongodb/node-mongodb-native@dce7939f86 (2026-08-11). Data as JSON: /api/errors/b4d94998d03ecd6b. Report an issue: GitHub.

Appendix: source

Thrown at src/cursor/abstract_cursor.ts:822

      throw new MongoTailableCursorError('Tailable cursor does not support batchSize');
    }

    if (typeof value !== 'number') {
      throw new MongoInvalidArgumentError('Operation "batchSize" requires an integer');
    }

    this.cursorOptions.batchSize = value;
    return this;
  }

  /**
   * Rewind this cursor to its uninitialized state. Any options that are present on the cursor will
   * remain in effect. Iterating this cursor will cause new queries to be sent to the server, even
   * if the resultant data has already been retrieved by this cursor.
   */
  rewind(): void {
    if (this.timeoutContext && this.timeoutContext.owner !== this) {
      throw new MongoAPIError(`Cannot rewind cursor that does not own its timeout context.`);
    }
    if (!this.initialized) {
      return;
    }

    this.cursorId = null;
    this.documents?.clear();
    this.timeoutContext?.clear();
    this.timeoutContext = undefined;
    this.isClosed = false;
    this.isKilled = false;
    this.initialized = false;
    this.hasEmittedClose = false;
    this.trackCursor();

    // We only want to end this session if we created it, and it hasn't ended yet
    if (this.cursorSession?.explicit === false) {
      if (!this.cursorSession.hasEnded) {

View on GitHub (pinned to dce7939f86)