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
- Make the callback async: await client.withTransaction(async (session) => { ... }).
- Ensure every async operation inside is awaited and the final promise is returned (in an async function, awaited ops propagate automatically).
- 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
- Always declare the withTransaction callback as async.
- Ensure every awaited op inside is actually awaited so the promise propagates.
- Add a lint rule or review step that flags non-async callbacks passed to withTransaction.
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
- Transaction already in progress
- input cluster time must have a valid "signature" property wi
- Transactions are not supported in snapshot sessions
- No transaction started
- Cannot call commitTransaction after calling abortTransaction
AI-assisted analysis of mongodb/node-mongodb-native@3366c21a63 (2026-08-04).
Data as JSON: /data/errors/32fa8fe223d5e625.json.
Report an issue: GitHub.