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 as a MongoAPIError in the AggregationCursor constructor (and again in addStage()) when timeoutMS is set, timeoutMode resolves to CursorTimeoutMode.ITERATION, and the pipeline's last stage is $out or $merge. $out and $merge are write stages that materialize results into a collection; under ITERATION mode the deadline resets per next() call, which does not bound the actual materialization work the server performs for those stages, so the driver forbids the combination to avoid a misleading timeout guarantee. The same check is applied lazily in addStage() so a stage added after construction is also caught.
Solutions
- Use CursorTimeoutMode.LIFETIME (or omit timeoutMode so a non-tailable aggregation defaults to LIFETIME) so the timeoutMS budget covers the whole $out/$merge operation.
- If you need per-iteration semantics, remove the $out/$merge stage and materialize results client-side.
- Set timeoutMS at the MongoClient/command level with LIFETIME semantics for materialization jobs instead of ITERATION on the cursor.
Example fix
// before: ITERATION mode + $out is rejected
const cursor = collection.aggregate(
[{ $match: { active: true } }, { $out: 'active_users' }],
{ timeoutMS: 5000, timeoutMode: 'iteration' }
);
// after: use LIFETIME (default for non-tailable) so the budget covers the write
const cursor = collection.aggregate(
[{ $match: { active: true } }, { $out: 'active_users' }],
{ timeoutMS: 5000 } // timeoutMode defaults to 'cursorLifetime'
); Defensive patterns
Strategy: validation
Validate before calling
function validateAggregationTimeout(pipeline: Document[], opts: AggregateOptions) {
const last = pipeline[pipeline.length - 1];
const writesToCollection = last?.$out != null || last?.$merge != null;
if (opts.timeoutMS != null && opts.timeoutMode === 'iteration' && writesToCollection) {
throw new RangeError('Use cursorLifetime (default) timeoutMode with $out/$merge, or remove timeoutMode.');
}
} Prevention
- For $out/$merge aggregations, omit timeoutMode so it defaults to cursorLifetime under timeoutMS.
- Run a pipeline-shape validator that flags write stages combined with ITERATION mode.
- Keep ETL/materialization aggregations on a separate options path from reporting aggregations.
When it happens
Trigger: db.collection.aggregate([{ $match: ... }, { $out: 'results' }], { timeoutMS: 5000 }) where the cursor defaults to ITERATION mode is fine only because $out cursors return immediately; the error specifically fires when timeoutMode is ITERATION. Concretely: options with timeoutMS + explicit timeoutMode: 'iteration' + a pipeline ending in $out/$merge, or building the cursor then calling .out(...) / .addStage({ $merge }) under those options.
Common situations: Adopting CSOT (timeoutMS) on an existing ETL aggregation that uses $merge/$out; copying ITERATION-mode options from a reporting aggregation into a materialization aggregation; chaining .out() on a cursor created with timeoutMS and explicit ITERATION mode.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Cannot set tailable cursor's timeoutMode to LIFETIME
- Cannot set timeoutMode without setting timeoutMS
- Cannot specify maxAwaitTimeMS >= timeoutMS for a tailable…
- Argument for maxTimeMS must be a number
- Argument "iterator" must be a function
AI-assisted analysis of mongodb/node-mongodb-native@dce7939f86 (2026-08-11).
Data as JSON: /api/errors/edf80cdfbadc4f81.
Report an issue: GitHub.
Appendix: 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 dce7939f86)