mongodb/node-mongodb-native · error · MongoInvalidArgumentError

An operation cannot be given a timeoutMS setting when inside

Error message

An operation cannot be given a timeoutMS setting when inside a withTransaction call that has a timeoutMS setting

What it means

Thrown by resolveOptions() when an operation is executed inside a 'convenient transaction' (a withTransaction() call that carries its own timeoutMS) AND the individual operation is also given a timeoutMS. The convenient-transaction API owns the timeout budget for the whole transaction, so a nested per-operation timeoutMS is contradictory and forbidden. It surfaces as a MongoInvalidArgumentError to prevent ambiguous/competing timeout semantics.

Source

Thrown at src/utils.ts:551

            wtimeout: undefined,
            wtimeoutMS: undefined
          }
        });
      }
      result.writeConcern = writeConcern;
    }
  }

  result.timeoutMS = timeoutMS;

  const readPreference = ReadPreference.fromOptions(options) ?? parent?.readPreference;
  if (readPreference) {
    result.readPreference = readPreference;
  }

  const isConvenientTransaction = session?.explicit && session?.timeoutContext != null;
  if (isConvenientTransaction && options?.timeoutMS != null) {
    throw new MongoInvalidArgumentError(
      'An operation cannot be given a timeoutMS setting when inside a withTransaction call that has a timeoutMS setting'
    );
  }

  return result;
}

export function isSuperset(set: Set<any> | any[], subset: Set<any> | any[]): boolean {
  set = Array.isArray(set) ? new Set(set) : set;
  subset = Array.isArray(subset) ? new Set(subset) : subset;
  for (const elem of subset) {
    if (!set.has(elem)) {
      return false;
    }
  }
  return true;
}

View on GitHub (pinned to 3366c21a63)

Solutions

  1. Remove the per-operation timeoutMS inside the withTransaction callback; let the transaction's timeoutMS govern.
  2. If a specific operation needs a shorter bound, enforce it with your own Promise.race/AbortController instead of timeoutMS.
  3. Audit shared option builders so timeoutMS is not spread into operations that run under withTransaction.

Example fix

// before
await session.withTransaction(
  async (session) => {
    await coll.insertOne({ a: 1 }, { session, timeoutMS: 1000 }); // throws
  },
  { timeoutMS: 5000 }
);

// after
await session.withTransaction(
  async (session) => {
    await coll.insertOne({ a: 1 }, { session }); // no nested timeoutMS
  },
  { timeoutMS: 5000 }
);
Defensive patterns

Strategy: validation

Validate before calling

function stripTimeoutInsideTx<T extends { timeoutMS?: number; session?: ClientSession }>(opts: T | undefined): T {
  if (opts?.session?.inTransaction() && opts.timeoutMS != null) {
    const { timeoutMS, ...rest } = opts;
    return rest as T;
  }
  return opts ?? ({} as T);
}

Type guard

function isInsideConvenientTransaction(session?: ClientSession): boolean {
  return !!session?.explicit && (session as any).timeoutContext != null;
}

Prevention

When it happens

Trigger: Inside `session.withTransaction(async (session) => { ... }, { timeoutMS: 5000 })`, calling any operation with an explicit `{ timeoutMS: N }` option, e.g. `coll.findOne({}, { session, timeoutMS: 1000 })`.

Common situations: Copying an operation helper that hardcodes timeoutMS into a withTransaction callback; layering the new timeoutMS API (v6+) onto existing transaction code without removing per-call timeouts; shared option objects spread into both transactional and non-transactional calls.

Related errors


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