mongodb/node-mongodb-native · error · MongoInvalidArgumentError

Cannot set tailable cursor's timeoutMode to LIFETIME

Error message

Cannot set tailable cursor's timeoutMode to LIFETIME

What it means

Thrown as a MongoInvalidArgumentError in the AbstractCursor constructor when timeoutMS is set, the cursor is tailable, and the caller explicitly sets timeoutMode to CursorTimeoutMode.LIFETIME ('cursorLifetime'). A tailable cursor has an unbounded lifetime by definition, so applying a single deadline across the whole cursor would either kill it almost immediately or defeat the purpose of tailing; the driver therefore only permits ITERATION mode for tailable cursors when timeoutMS is in effect. The guard is hit only when the caller overrides the default, because tailable cursors default to ITERATION automatically.

Solutions

  1. Remove the explicit timeoutMode so the driver defaults the tailable cursor to ITERATION.
  2. If you genuinely need a bounded total lifetime, set timeoutMode: 'iteration' (or omit it) and enforce a wall-clock cap in your own loop.
  3. Re-evaluate whether the cursor needs to be tailable at all under a total-time budget.

Example fix

// before: tailable + explicit LIFETIME is rejected
const cursor = collection.find(filter, {
  tailable: true,
  awaitData: true,
  timeoutMS: 1000,
  timeoutMode: 'cursorLifetime'
});

// after: let the driver pick ITERATION (default for tailable)
const cursor = collection.find(filter, {
  tailable: true,
  awaitData: true,
  timeoutMS: 1000
});
Defensive patterns

Strategy: validation

Validate before calling

// Enforce driver rule: tailable cursors may only use ITERATION mode
if (opts.tailable && opts.timeoutMS != null && opts.timeoutMode === 'cursorLifetime') {
  throw new RangeError('Tailable cursors cannot use timeoutMode cursorLifetime; remove timeoutMode or use iteration.');
}

Prevention

When it happens

Trigger: collection.find(filter, { tailable: true, timeoutMS: 1000, timeoutMode: 'cursorLifetime' }); constructing a tailable change-stream-like cursor while trying to force a whole-cursor deadline.

Common situations: Copying timeoutMode from a non-tailable cursor config into a tailable one; reading the LIFETIME example in the JSDoc and applying it to a tailable cursor without noticing the tailable default is ITERATION.

Understand the failure class

Related errors


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

Appendix: source

Thrown at src/cursor/abstract_cursor.ts:302

      if (options.timeoutMode == null) {
        if (options.tailable) {
          if (options.awaitData) {
            if (
              options.maxAwaitTimeMS != null &&
              options.maxAwaitTimeMS >= this.cursorOptions.timeoutMS
            )
              throw new MongoInvalidArgumentError(
                'Cannot specify maxAwaitTimeMS >= timeoutMS for a tailable awaitData cursor'
              );
          }

          this.cursorOptions.timeoutMode = CursorTimeoutMode.ITERATION;
        } else {
          this.cursorOptions.timeoutMode = CursorTimeoutMode.LIFETIME;
        }
      } else {
        if (options.tailable && options.timeoutMode === CursorTimeoutMode.LIFETIME) {
          throw new MongoInvalidArgumentError(
            "Cannot set tailable cursor's timeoutMode to LIFETIME"
          );
        }
        this.cursorOptions.timeoutMode = options.timeoutMode;
      }
    } else {
      if (options.timeoutMode != null)
        throw new MongoInvalidArgumentError('Cannot set timeoutMode without setting timeoutMS');
    }

    // Set for initial command
    this.cursorOptions.omitMaxTimeMS =
      this.cursorOptions.timeoutMS != null &&
      ((this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION &&
        !this.cursorOptions.tailable) ||
        (this.cursorOptions.tailable && !this.cursorOptions.awaitData));

    const readConcern = ReadConcern.fromOptions(options);

View on GitHub (pinned to dce7939f86)