mongodb/node-mongodb-native · error · MongoInvalidArgumentError

Cannot specify maxAwaitTimeMS >= timeoutMS for a tailable…

Error message

Cannot specify maxAwaitTimeMS >= timeoutMS for a tailable awaitData cursor

What it means

Thrown as a MongoInvalidArgumentError in the AbstractCursor constructor when timeoutMS is set, the cursor is tailable with awaitData, and a user-supplied maxAwaitTimeMS is greater than or equal to timeoutMS. For a tailable awaitData cursor the driver picks ITERATION timeout mode, meaning each getMore is bounded by timeoutMS; if the server is allowed to block for maxAwaitTimeMS >= timeoutMS, the getMore would blow past its own per-iteration deadline, so the driver rejects the configuration up front. This guards an otherwise silent time-budget contradiction.

Solutions

  1. Make maxAwaitTimeMS strictly less than timeoutMS (e.g. timeoutMS: 1000, maxAwaitTimeMS: 500).
  2. If you want the server to block as long as the whole budget, drop timeoutMS and rely on maxAwaitTimeMS alone (CSOT disabled).
  3. If you want CSOT semantics only, omit maxAwaitTimeMS and let timeoutMS drive the await window.
  4. Compute the values from a single source constant so the invariant maxAwaitTimeMS < timeoutMS is impossible to violate by hand-edit.

Example fix

// before: 2000 >= 2000 violates the invariant
const cursor = collection.find(filter, {
  tailable: true,
  awaitData: true,
  timeoutMS: 2000,
  maxAwaitTimeMS: 2000
});

// after: await window is strictly inside the iteration budget
const cursor = collection.find(filter, {
  tailable: true,
  awaitData: true,
  timeoutMS: 2000,
  maxAwaitTimeMS: 1000
});
Defensive patterns

Strategy: validation

Validate before calling

function validateTailableTimeoutOpts(opts: {
  tailable?: boolean; awaitData?: boolean; timeoutMS?: number; maxAwaitTimeMS?: number;
}) {
  if (opts.tailable && opts.awaitData && opts.timeoutMS != null && opts.maxAwaitTimeMS != null) {
    if (opts.maxAwaitTimeMS >= opts.timeoutMS) {
      throw new RangeError(`maxAwaitTimeMS (${opts.maxAwaitTimeMS}) must be < timeoutMS (${opts.timeoutMS})`);
    }
  }
}

Prevention

When it happens

Trigger: Calling collection.find(..., { tailable: true, awaitData: true, timeoutMS: <n>, maxAwaitTimeMS: <m> }) with m >= n; building a change-stream-like tailable cursor with explicit maxAwaitTimeMS and a CSOT timeoutMS budget where the await window meets or exceeds the budget.

Common situations: Migrating an existing tailable/awaitData cursor to Client-Side Operation Timeouts (CSOT, timeoutMS) and leaving an old maxAwaitTimeMS in place; copying options between cursors where the same number is used for both fields; setting timeoutMS equal to maxAwaitTimeMS by accident.

Understand the failure class

Related errors


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

Appendix: source

Thrown at src/cursor/abstract_cursor.ts:291

          ? options.readPreference
          : ReadPreference.primary,
      ...pluckBSONSerializeOptions(options),
      timeoutMS: options?.timeoutContext?.csotEnabled()
        ? options.timeoutContext.timeoutMS
        : options.timeoutMS,
      tailable: options.tailable,
      awaitData: options.awaitData
    };

    if (this.cursorOptions.timeoutMS != null) {
      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)

View on GitHub (pinned to dce7939f86)