mongodb/node-mongodb-native · error · MongoOperationTimeoutError

Expired after ${this.timeoutMS}ms

Error message

Expired after ${this.timeoutMS}ms

What it means

Thrown by the @internal CSOTTimeoutContext.getRemainingTimeMSOrThrow() when remainingTimeMS <= 0, i.e. the CSOT deadline has passed. The message includes the original timeoutMS. It surfaces as a MongoOperationTimeoutError and is the primary way CSOT-enforced operations fail once their budget is exhausted.

Source

Thrown at src/timeout.ts:314

    this.minRoundTripTime = 0;
    this._serverSelectionTimeout?.clear();
    this._connectionCheckoutTimeout?.clear();
  }

  clear(): void {
    this._serverSelectionTimeout?.clear();
    this._connectionCheckoutTimeout?.clear();
  }

  /**
   * @internal
   * Throws a MongoOperationTimeoutError if the context has expired.
   * If the context has not expired, returns the `remainingTimeMS`
   **/
  getRemainingTimeMSOrThrow(message?: string): number {
    const { remainingTimeMS } = this;
    if (remainingTimeMS <= 0)
      throw new MongoOperationTimeoutError(message ?? `Expired after ${this.timeoutMS}ms`);
    return remainingTimeMS;
  }

  /**
   * @internal
   * This method is intended to be used in situations where concurrent operation are on the same deadline, but cannot share a single `TimeoutContext` instance.
   * Returns a new instance of `CSOTTimeoutContext` constructed with identical options, but setting the `start` property to `this.start`.
   */
  clone(): CSOTTimeoutContext {
    const timeoutContext = new CSOTTimeoutContext({
      timeoutMS: this.timeoutMS,
      serverSelectionTimeoutMS: this.serverSelectionTimeoutMS
    });
    timeoutContext.start = this.start;
    return timeoutContext;
  }

  override refreshed(): CSOTTimeoutContext {

View on GitHub (pinned to 3366c21a63)

Solutions

  1. Increase timeoutMS to match realistic operation latency.
  2. Optimize the operation (indexes, $project to reduce payload, narrower filters) to fit the budget.
  3. For transactions, size timeoutMS to the whole transaction, not a single op, since withTransaction shares one deadline.
  4. Scale the cluster (more CPU/replicas) if the workload legitimately needs more time.

Example fix

// before
await coll.aggregate([ /* heavy pipeline */ ], { timeoutMS: 1000 }).toArray(); // throws Expired after 1000ms

// after
await coll.aggregate([ /* heavy pipeline */ ], { timeoutMS: 60_000 }).toArray();
Defensive patterns

Strategy: retry

Validate before calling

// size timeoutMS to realistic latency; budget the whole transaction
const timeoutMS = Math.max(30_000, estimatedMs * 3);
await coll.find({}, { timeoutMS }).toArray();

Type guard

import { MongoOperationTimeoutError } from 'mongodb';
function isOperationTimeout(e: unknown): e is MongoOperationTimeoutError {
  return e instanceof MongoOperationTimeoutError;
}

Try / catch

try {
  await coll.aggregate(pipeline, { timeoutMS: 5_000 }).toArray();
} catch (e) {
  if (isOperationTimeout(e)) {
    // retry with a larger budget or surface 'query too slow for deadline'
    await coll.aggregate(pipeline, { timeoutMS: 60_000 }).toArray();
  } else throw e;
}

Prevention

When it happens

Trigger: Any operation running under timeoutMS (CSOT) whose wall-clock budget runs out before completion: slow queries, long server selection, slow connection checkout, or chained operations sharing one deadline.

Common situations: Setting an aggressive timeoutMS on heavy aggregations/queries; network latency or cluster load pushing operations past the budget; sequential operations inside withTransaction({ timeoutMS }) exhausting the shared budget.

Related errors


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