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 aggregateOptions

What it means

Thrown by AggregationCursor._initialize() (MongoAPIError) when explain is present in aggregateOptions and the explain/timeout option combination is invalid — specifically when validateExplainTimeoutOptions() detects timeoutMS used together with maxTimeMS (or explain.maxTimeMS). The driver forbids combining the modern timeoutMS with the legacy maxTimeMS on an explained aggregate command.

Source

Thrown at src/cursor/aggregation_cursor.ts:84

  }

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

  /** @internal */
  async _initialize(session: ClientSession): Promise<InitialCursorResponse> {
    const options = {
      ...this.aggregateOptions,
      ...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 aggregateOptions'
        );
      }
    }

    const aggregateOperation = new AggregateOperation(this.namespace, this.pipeline, options);

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

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

  /** Execute the explain for the cursor */
  async explain(): Promise<Document>;
  async explain(verbosity: ExplainVerbosityLike | ExplainCommandOptions): Promise<Document>;
  async explain(options: { timeoutMS?: number }): Promise<Document>;
  async explain(
    verbosity: ExplainVerbosityLike | ExplainCommandOptions,

View on GitHub (pinned to 3366c21a63)

Solutions

  1. Use EITHER timeoutMS OR maxTimeMS, not both, on explained aggregations
  2. Drop maxTimeMS/explain.maxTimeMS from the options when using timeoutMS
  3. Call cursor.explain() instead of putting explain in aggregateOptions — explain() manages timeout separately

Example fix

// before
coll.aggregate(p, { explain: true, timeoutMS: 1000, maxTimeMS: 500 });
// after
coll.aggregate(p, { explain: true, maxTimeMS: 500 }); // or just timeoutMS
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.aggregate(p, 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.aggregate(pipeline, { explain: true, timeoutMS: N, maxTimeMS: M }) or explain.maxTimeMS set. Triggered lazily when the cursor initializes (first next()/toArray()).

Common situations: Migrating from maxTimeMS to timeoutMS while leaving explain/maxTimeMS in a shared options object; passing a generic query-options preset to both explained and non-explained queries.

Related errors


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