mongodb/node-mongodb-native · error · MongoAPIError

timeoutMS cannot be used with explain when explain is specif

Error message

timeoutMS cannot be used with explain when explain is specified in findOptions

What it means

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.

Source

Thrown at src/cursor/find_cursor.ts:84

  override map<T>(transform: (doc: TSchema) => T): FindCursor<T> {
    return super.map(transform) as FindCursor<T>;
  }

  /** @internal */
  async _initialize(session: ClientSession): Promise<InitialCursorResponse> {
    const options = {
      ...this.findOptions, // NOTE: order matters here, we may need to refine this
      ...this.cursorOptions,
      session,
      signal: this.signal
    };

    if (options.explain) {
      try {
        validateExplainTimeoutOptions(options, Explain.fromOptions(options));
      } catch {
        throw new MongoAPIError(
          'timeoutMS cannot be used with explain when explain is specified in findOptions'
        );
      }
    }

    const findOperation = new FindOperation(this.namespace, this.cursorFilter, options);

    const response = await executeOperation(this.client, findOperation, this.timeoutContext);

    // the response is not a cursor when `explain` is enabled
    this.numReturned = response.batchSize;

    return { server: findOperation.server, session, response };
  }

  /** @internal */
  override async getMore(): Promise<CursorResponse> {
    const numReturned = this.numReturned;

View on GitHub (pinned to 3366c21a63)

Solutions

  1. Pick one timeout mechanism: use timeoutMS alone, or maxTimeMS alone
  2. Remove explain from findOptions and call cursor.explain() which handles timeouts
  3. Strip maxTimeMS/explain.maxTimeMS before constructing the cursor when using timeoutMS

Example fix

// before
coll.find({}, { explain: true, timeoutMS: 1000, maxTimeMS: 500 });
// after
coll.find({}, { explain: true, maxTimeMS: 500 });
Defensive patterns

Strategy: validation

Validate before calling

function cleanExplainTimeout(opts) {
  if (opts.explain && opts.timeoutMS != null) {
    const { maxTimeMS, explain, ...rest } = opts;
    return { ...rest, explain: typeof explain === 'object' ? { ...explain, maxTimeMS: undefined } : explain };
  }
  return opts;
}
coll.find(filter, cleanExplainTimeout(opts));

Type guard

const hasConflictingTimeout = (o) => o.explain != null && o.timeoutMS != null && (o.maxTimeMS != null || o.explain?.maxTimeMS != null);

Prevention

When it happens

Trigger: collection.find(filter, { explain: true, timeoutMS: N, maxTimeMS: M }) or explain.maxTimeMS set. Fires when the cursor initializes on first iteration.

Common situations: Shared query-options presets mixing legacy maxTimeMS with new timeoutMS; explaining a query for debugging while a default timeout is globally applied.

Related errors


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