{"id":"d1fb15dd70aff16c","repo":"mongodb/node-mongodb-native","slug":"cannot-specify-maxawaittimems-timeoutms-for-a-t","errorCode":null,"errorMessage":"Cannot specify maxAwaitTimeMS >= timeoutMS for a tailable awaitData cursor","messagePattern":"Cannot specify maxAwaitTimeMS >= timeoutMS for a tailable awaitData cursor","errorType":"exception","errorClass":"MongoInvalidArgumentError","httpStatus":null,"severity":"error","filePath":"src/cursor/abstract_cursor.ts","lineNumber":291,"sourceCode":"          ? options.readPreference\n          : ReadPreference.primary,\n      ...pluckBSONSerializeOptions(options),\n      timeoutMS: options?.timeoutContext?.csotEnabled()\n        ? options.timeoutContext.timeoutMS\n        : options.timeoutMS,\n      tailable: options.tailable,\n      awaitData: options.awaitData\n    };\n\n    if (this.cursorOptions.timeoutMS != null) {\n      if (options.timeoutMode == null) {\n        if (options.tailable) {\n          if (options.awaitData) {\n            if (\n              options.maxAwaitTimeMS != null &&\n              options.maxAwaitTimeMS >= this.cursorOptions.timeoutMS\n            )\n              throw new MongoInvalidArgumentError(\n                'Cannot specify maxAwaitTimeMS >= timeoutMS for a tailable awaitData cursor'\n              );\n          }\n\n          this.cursorOptions.timeoutMode = CursorTimeoutMode.ITERATION;\n        } else {\n          this.cursorOptions.timeoutMode = CursorTimeoutMode.LIFETIME;\n        }\n      } else {\n        if (options.tailable && options.timeoutMode === CursorTimeoutMode.LIFETIME) {\n          throw new MongoInvalidArgumentError(\n            \"Cannot set tailable cursor's timeoutMode to LIFETIME\"\n          );\n        }\n        this.cursorOptions.timeoutMode = options.timeoutMode;\n      }\n    } else {\n      if (options.timeoutMode != null)","sourceCodeStart":273,"sourceCodeEnd":309,"githubUrl":"https://github.com/mongodb/node-mongodb-native/blob/3366c21a6311e02f1be91da982f9b93d3cce99a0/src/cursor/abstract_cursor.ts#L273-L309","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Make maxAwaitTimeMS strictly less than timeoutMS (e.g. timeoutMS=1000, maxAwaitTimeMS=500).","Drop maxAwaitTimeMS and let the driver derive it from timeoutMS.","If you want a single lifetime budget, remove tailable/awaitData so LIFETIME mode applies."],"exampleFix":"// before\ncollection.find(filter, { tailable: true, awaitData: true, timeoutMS: 1000, maxAwaitTimeMS: 1000 });\n// after\ncollection.find(filter, { tailable: true, awaitData: true, timeoutMS: 1000, maxAwaitTimeMS: 500 });","handlingStrategy":"validation","validationCode":"function checkTailableTimeouts(opts) {\n  if (opts.tailable && opts.awaitData && opts.timeoutMS != null && opts.maxAwaitTimeMS != null) {\n    return opts.maxAwaitTimeMS < opts.timeoutMS;\n  }\n  return true;\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["For tailable+awaitData cursors, keep maxAwaitTimeMS strictly below timeoutMS.","Prefer setting only timeoutMS and let the driver derive maxAwaitTimeMS.","Unit-test option builders that target capped-collection tailing."],"tags":["cursor","timeout","csot","tailable","options"],"analyzedSha":"3366c21a6311e02f1be91da982f9b93d3cce99a0","analyzedAt":"2026-08-04T13:40:15.335Z","schemaVersion":2}