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
- Make maxAwaitTimeMS strictly less than timeoutMS (e.g. timeoutMS: 1000, maxAwaitTimeMS: 500).
- If you want the server to block as long as the whole budget, drop timeoutMS and rely on maxAwaitTimeMS alone (CSOT disabled).
- If you want CSOT semantics only, omit maxAwaitTimeMS and let timeoutMS drive the await window.
- 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
- Treat maxAwaitTimeMS and timeoutMS as derived from a single config: maxAwaitTimeMS = floor(timeoutMS / 2).
- Run a constructor-options validation pass before passing options to find().
- When migrating to CSOT, audit existing maxAwaitTimeMS values on tailable cursors.
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
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Cannot set tailable cursor's timeoutMode to LIFETIME
- Cannot set timeoutMode without setting timeoutMS
- Cannot use $out or $merge stage with ITERATION timeoutMode
- Tailable cursor does not support batchSize
- An operation cannot be given a timeoutMS setting when…
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)