{"id":"045d6db349ec898e","repo":"mongodb/node-mongodb-native","slug":"timeoutms-cannot-be-used-with-explain-when-explain-045d6d","errorCode":null,"errorMessage":"timeoutMS cannot be used with explain when explain is specified in findOptions","messagePattern":"timeoutMS cannot be used with explain when explain is specified in findOptions","errorType":"exception","errorClass":"MongoAPIError","httpStatus":null,"severity":"error","filePath":"src/cursor/find_cursor.ts","lineNumber":84,"sourceCode":"\n  override map<T>(transform: (doc: TSchema) => T): FindCursor<T> {\n    return super.map(transform) as FindCursor<T>;\n  }\n\n  /** @internal */\n  async _initialize(session: ClientSession): Promise<InitialCursorResponse> {\n    const options = {\n      ...this.findOptions, // NOTE: order matters here, we may need to refine this\n      ...this.cursorOptions,\n      session,\n      signal: this.signal\n    };\n\n    if (options.explain) {\n      try {\n        validateExplainTimeoutOptions(options, Explain.fromOptions(options));\n      } catch {\n        throw new MongoAPIError(\n          'timeoutMS cannot be used with explain when explain is specified in findOptions'\n        );\n      }\n    }\n\n    const findOperation = new FindOperation(this.namespace, this.cursorFilter, options);\n\n    const response = await executeOperation(this.client, findOperation, this.timeoutContext);\n\n    // the response is not a cursor when `explain` is enabled\n    this.numReturned = response.batchSize;\n\n    return { server: findOperation.server, session, response };\n  }\n\n  /** @internal */\n  override async getMore(): Promise<CursorResponse> {\n    const numReturned = this.numReturned;","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/mongodb/node-mongodb-native/blob/3366c21a6311e02f1be91da982f9b93d3cce99a0/src/cursor/find_cursor.ts#L66-L102","documentation":"Thrown by FindCursor._initialize() (MongoAPIError) when explain is set in findOptions and validateExplainTimeoutOptions() rejects the option mix — i.e. timeoutMS combined with maxTimeMS (or explain.maxTimeMS) on an explained find. Mirrors the aggregation explain-timeout rule.","triggerScenarios":"collection.find(filter, { explain: true, timeoutMS: N, maxTimeMS: M }) or explain.maxTimeMS set. Fires when the cursor initializes on first iteration.","commonSituations":"Shared query-options presets mixing legacy maxTimeMS with new timeoutMS; explaining a query for debugging while a default timeout is globally applied.","solutions":["Pick one timeout mechanism: use timeoutMS alone, or maxTimeMS alone","Remove explain from findOptions and call cursor.explain() which handles timeouts","Strip maxTimeMS/explain.maxTimeMS before constructing the cursor when using timeoutMS"],"exampleFix":"// before\ncoll.find({}, { explain: true, timeoutMS: 1000, maxTimeMS: 500 });\n// after\ncoll.find({}, { explain: true, maxTimeMS: 500 });","handlingStrategy":"validation","validationCode":"function cleanExplainTimeout(opts) {\n  if (opts.explain && opts.timeoutMS != null) {\n    const { maxTimeMS, explain, ...rest } = opts;\n    return { ...rest, explain: typeof explain === 'object' ? { ...explain, maxTimeMS: undefined } : explain };\n  }\n  return opts;\n}\ncoll.find(filter, cleanExplainTimeout(opts));","typeGuard":"const hasConflictingTimeout = (o) => o.explain != null && o.timeoutMS != null && (o.maxTimeMS != null || o.explain?.maxTimeMS != null);","tryCatchPattern":null,"preventionTips":["Use either timeoutMS or maxTimeMS, never both, on explained finds","Prefer cursor.explain() over explain in findOptions","Sanitize shared option presets before explained queries"],"tags":["find","explain","timeout","timeoutms","maxtimems"],"analyzedSha":"3366c21a6311e02f1be91da982f9b93d3cce99a0","analyzedAt":"2026-08-04T13:40:15.335Z","schemaVersion":2}