mongodb/node-mongodb-native · error · MongoAPIError

Cannot use $out or $merge stage with ITERATION timeoutMode

Error message

Cannot use $out or $merge stage with ITERATION timeoutMode

What it means

Thrown by the AggregationCursor constructor (MongoAPIError) when timeoutMS is set, timeoutMode is ITERATION, and the final pipeline stage is $merge or $out. ITERATION mode resets the timeout per getMore batch, but $out/$merge write the entire result set in one shot on the first batch, so a per-iteration timeout cannot meaningfully bound them. The driver rejects the combination at construction time.

Source

Thrown at src/cursor/aggregation_cursor.ts:57

  constructor(
    client: MongoClient,
    namespace: MongoDBNamespace,
    pipeline: Document[] = [],
    options: AggregateOptions & Abortable = {}
  ) {
    super(client, namespace, options);

    this.pipeline = pipeline;
    this.aggregateOptions = options;

    const lastStage: Document | undefined = this.pipeline[this.pipeline.length - 1];

    if (
      this.cursorOptions.timeoutMS != null &&
      this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION &&
      (lastStage?.$merge != null || lastStage?.$out != null)
    )
      throw new MongoAPIError('Cannot use $out or $merge stage with ITERATION timeoutMode');
  }

  clone(): AggregationCursor<TSchema> {
    const clonedOptions = mergeOptions({}, this.aggregateOptions);
    delete clonedOptions.session;
    return new AggregationCursor(this.client, this.namespace, this.pipeline, {
      ...clonedOptions
    });
  }

  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,

View on GitHub (pinned to 3366c21a63)

Solutions

  1. Omit timeoutMode (or set it to 'cursorLifetime') when the pipeline ends with $out/$merge
  2. Remove timeoutMS for $out/$merge pipelines, or wrap the whole operation in your own Promise.race timeout
  3. If you need a per-batch bound, restructure the pipeline so the write stage is not the terminal stage

Example fix

// before
coll.aggregate([{ ... }, { $out: 'result' }], { timeoutMS: 5000, timeoutMode: 'iteration' });
// after
coll.aggregate([{ ... }, { $out: 'result' }], { timeoutMS: 5000 }); // LIFETIME (default)
Defensive patterns

Strategy: validation

Validate before calling

function safeAggregate(coll, pipeline, opts) {
  const last = pipeline[pipeline.length - 1];
  const hasWriteStage = last && (last.$out != null || last.$merge != null);
  if (opts?.timeoutMS != null && opts?.timeoutMode === 'iteration' && hasWriteStage) {
    const { timeoutMode, ...rest } = opts;
    return coll.aggregate(pipeline, rest); // drop iteration mode
  }
  return coll.aggregate(pipeline, opts);
}

Type guard

const isWriteStage = (s) => s != null && (s.$out != null || s.$merge != null);
const canUseIterationMode = (pipeline) => !isWriteStage(pipeline[pipeline.length - 1]);

Prevention

When it happens

Trigger: collection.aggregate(pipeline, { timeoutMS: N, timeoutMode: 'iteration' }) where pipeline ends with { $out: 'coll' } or { $merge: ... }. Also triggered if you pass timeoutMS without timeoutMode on a non-tailable aggregation, because the driver defaults non-tailable+timeoutMS to LIFETIME — but explicitly setting 'iteration' with $out/$merge is the direct hit.

Common situations: Building a materialized view with $out/$merge under a global timeout policy; inheriting timeoutMS from a shared options object and forcing iteration mode.

Related errors


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