{"id":"59990a3a3afd14e9","repo":"mongodb/node-mongodb-native","slug":"cannot-set-tailable-cursor-s-timeoutmode-to-lifeti","errorCode":null,"errorMessage":"Cannot set tailable cursor's timeoutMode to LIFETIME","messagePattern":"Cannot set tailable cursor's timeoutMode to LIFETIME","errorType":"exception","errorClass":"MongoInvalidArgumentError","httpStatus":null,"severity":"error","filePath":"src/cursor/abstract_cursor.ts","lineNumber":302,"sourceCode":"      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)\n        throw new MongoInvalidArgumentError('Cannot set timeoutMode without setting timeoutMS');\n    }\n\n    // Set for initial command\n    this.cursorOptions.omitMaxTimeMS =\n      this.cursorOptions.timeoutMS != null &&\n      ((this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION &&\n        !this.cursorOptions.tailable) ||\n        (this.cursorOptions.tailable && !this.cursorOptions.awaitData));\n\n    const readConcern = ReadConcern.fromOptions(options);","sourceCodeStart":284,"sourceCodeEnd":320,"githubUrl":"https://github.com/mongodb/node-mongodb-native/blob/3366c21a6311e02f1be91da982f9b93d3cce99a0/src/cursor/abstract_cursor.ts#L284-L320","documentation":"Thrown when a tailable cursor is constructed with timeoutMode explicitly set to LIFETIME. Tailable cursors are incompatible with a single lifetime timeout budget — they must use ITERATION mode so each getMore gets a fresh time slice. This guard fires only when the caller explicitly passes timeoutMode.","triggerScenarios":"Passing { tailable: true, timeoutMS: 1000, timeoutMode: CursorTimeoutMode.LIFETIME } together. The combination is contradictory because tailable cursors are long-lived by design.","commonSituations":"Hardcoding timeoutMode from a shared config helper that defaults to LIFETIME; migrating to CSOT and forgetting that tailable cursors need ITERATION mode.","solutions":["Omit timeoutMode for tailable cursors — the driver auto-selects ITERATION when tailable+awaitData and timeoutMS is set.","If you must pass it explicitly, use CursorTimeoutMode.ITERATION for any tailable cursor.","Remove tailable if you genuinely want a LIFETIME-bounded query."],"exampleFix":"// before\ncollection.find(filter, { tailable: true, awaitData: true, timeoutMS: 1000, timeoutMode: CursorTimeoutMode.LIFETIME });\n// after\ncollection.find(filter, { tailable: true, awaitData: true, timeoutMS: 1000, timeoutMode: CursorTimeoutMode.ITERATION });","handlingStrategy":"validation","validationCode":"function checkTimeoutMode(opts) {\n  if (opts.tailable && opts.timeoutMode === 'lifetime') return false;\n  return true;\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Omit timeoutMode for tailable cursors — the driver selects ITERATION automatically.","Never share a generic 'lifetime timeout' config across all cursor types.","Audit option helpers that inject timeoutMode unconditionally."],"tags":["cursor","timeout","csot","tailable","options"],"analyzedSha":"3366c21a6311e02f1be91da982f9b93d3cce99a0","analyzedAt":"2026-08-04T13:40:15.335Z","schemaVersion":2}