mongodb/node-mongodb-native · error · MongoInvalidArgumentError

Cannot specify maxAwaitTimeMS >= timeoutMS for a tailable aw

Error message

Cannot specify maxAwaitTimeMS >= timeoutMS for a tailable awaitData cursor

What it means

Thrown during cursor construction when the cursor is tailable with awaitData, has a timeoutMS set, and maxAwaitTimeMS is greater than or equal to timeoutMS. For iteration-timeout tailable cursors, maxAwaitTimeMS must be strictly less than timeoutMS so the await window fits inside the per-iteration budget.

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 3366c21a63)

Solutions

  1. Make maxAwaitTimeMS strictly less than timeoutMS (e.g. timeoutMS=1000, maxAwaitTimeMS=500).
  2. Drop maxAwaitTimeMS and let the driver derive it from timeoutMS.
  3. If you want a single lifetime budget, remove tailable/awaitData so LIFETIME mode applies.

Example fix

// before
collection.find(filter, { tailable: true, awaitData: true, timeoutMS: 1000, maxAwaitTimeMS: 1000 });
// after
collection.find(filter, { tailable: true, awaitData: true, timeoutMS: 1000, maxAwaitTimeMS: 500 });
Defensive patterns

Strategy: validation

Validate before calling

function checkTailableTimeouts(opts) {
  if (opts.tailable && opts.awaitData && opts.timeoutMS != null && opts.maxAwaitTimeMS != null) {
    return opts.maxAwaitTimeMS < opts.timeoutMS;
  }
  return true;
}

Prevention

When it happens

Trigger: Calling collection.find({}, { tailable: true, awaitData: true, timeoutMS: 1000, maxAwaitTimeMS: 1000 }) or any combination where maxAwaitTimeMS >= timeoutMS on a tailable+awaitData cursor (typical for change-stream-like polling on capped collections).

Common situations: Tuning change-stream tailing latency and setting both timeouts to the same value; migrating a legacy maxAwaitTimeMS config and adding timeoutMS for CSOT without adjusting the former.

Related errors


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