mongodb/node-mongodb-native · error · MongoInvalidArgumentError

Function provided to `withTransaction` must return a Promise

Error message

Function provided to `withTransaction` must return a Promise

What it means

Thrown by ClientSession.withTransaction() after invoking the user callback: if the callback's return value is not a Promise (thenable), the driver cannot await it and cannot sequence commit/retry. withTransaction relies on the callback returning a promise that propagates operation rejections. It is a MongoInvalidArgumentError.

Source

Thrown at src/sessions.ts:819

          }

          await setTimeout(backoffMS);
        }

        // 3. Invoke startTransaction on the session and increment transactionAttempt. If TransactionOptions were
        // specified in the call to withTransaction, those MUST be used for startTransaction. Note that
        // ClientSession.defaultTransactionOptions will be used in the absence of any explicit TransactionOptions.
        // 4. If startTransaction reported an error, propagate that error to the caller of withTransaction as is and
        // return immediately.
        this.startTransaction(options);

        try {
          // 5. Invoke the callback. Drivers MUST ensure that the ClientSession can be accessed within the callback
          // (e.g. pass ClientSession as the first parameter, rely on lexical scoping). Drivers MAY pass additional
          // parameters as needed (e.g. user data solicited by withTransaction).
          const promise = fn(this);
          if (!isPromiseLike(promise)) {
            throw new MongoInvalidArgumentError(
              'Function provided to `withTransaction` must return a Promise'
            );
          }

          // 6. Control returns to withTransaction. Determine the current state of the ClientSession and whether the
          // callback reported an error (e.g. thrown exception, error output parameter).
          result = await promise;

          // 8. If the ClientSession is in the "no transaction", "transaction aborted", or "transaction committed"
          // state, assume the callback intentionally aborted or committed the transaction and return immediately.
          if (
            this.transaction.state === TxnState.NO_TRANSACTION ||
            this.transaction.state === TxnState.TRANSACTION_COMMITTED ||
            this.transaction.state === TxnState.TRANSACTION_ABORTED
          ) {
            return result;
          }
        } catch (fnError) {

View on GitHub (pinned to 3366c21a63)

Solutions

  1. Make the callback async: await client.withTransaction(async (session) => { ... }).
  2. Ensure every async operation inside is awaited and the final promise is returned (in an async function, awaited ops propagate automatically).
  3. If using a non-async arrow, explicitly return a Promise: (session) => Promise.resolve(...).

Example fix

// before
await session.withTransaction((session) => {
  coll.insertOne(doc, { session }); // returns undefined, not a promise -> throws
});

// after
await session.withTransaction(async (session) => {
  await coll.insertOne(doc, { session });
});
Defensive patterns

Strategy: type-guard

Validate before calling

function isPromiseLike<T>(v: unknown): v is PromiseLike<T> {
  return v != null && typeof (v as any).then === 'function';
}
const cb = (session) => { /* ... */ };
if (isPromiseLike(cb(session))) {
  // safe to pass to withTransaction shape
}

Type guard

function isAsyncFn(fn: unknown): fn is (...args: any[]) => Promise<any> {
  return typeof fn === 'function' && fn.constructor?.name === 'AsyncFunction';
}

Try / catch

try {
  await session.withTransaction(fn);
} catch (e) {
  if (e instanceof MongoInvalidArgumentError && /must return a Promise/.test(e.message)) {
    // wrap fn in async and retry: await session.withTransaction(async (s) => fn(s))
  } else throw e;
}

Prevention

When it happens

Trigger: Passing a synchronous (non-async) function to withTransaction that returns undefined or a plain value; a callback that forgets `return` on its async operations; an arrow function that does work but returns nothing.

Common situations: Forgetting the `async` keyword on the withTransaction callback; callback that calls awaitable ops without returning them; refactoring an async function into a sync one.

Related errors


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